/*
  ==============================================================================

   This file is part of the JUCE framework.
   Copyright (c) Raw Material Software Limited

   JUCE is an open source framework subject to commercial or open source
   licensing.

   By downloading, installing, or using the JUCE framework, or combining the
   JUCE framework with any other source code, object code, content or any other
   copyrightable work, you agree to the terms of the JUCE End User Licence
   Agreement, and all incorporated terms including the JUCE Privacy Policy and
   the JUCE Website Terms of Service, as applicable, which will bind you. If you
   do not agree to the terms of these agreements, we will not license the JUCE
   framework to you, and you must discontinue the installation or download
   process and cease use of the JUCE framework.

   JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
   JUCE Privacy Policy: https://juce.com/juce-privacy-policy
   JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/

   Or:

   You may also use this code under the terms of the AGPLv3:
   https://www.gnu.org/licenses/agpl-3.0.en.html

   THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
   WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

constexpr unsigned char javaMidiByteCode[] {
    0x1f, 0x8b, 0x08, 0x08, 0x90, 0xda, 0xee, 0x67, 0x00, 0x03, 0x63, 0x6c,
    0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0xad, 0x7c,
    0x09, 0x7c, 0x94, 0xd5, 0xd5, 0xf7, 0xb9, 0xcf, 0xcc, 0x64, 0x87, 0x4c,
    0x26, 0x21, 0x09, 0x01, 0x92, 0xc9, 0x24, 0x40, 0x02, 0x64, 0x4f, 0x20,
    0x21, 0x01, 0xb3, 0x22, 0x81, 0x00, 0x31, 0x99, 0x44, 0x21, 0x52, 0x1c,
    0x92, 0x81, 0x0c, 0x4c, 0x66, 0xc6, 0x99, 0x09, 0x8b, 0xb5, 0x15, 0x97,
    0x56, 0xac, 0x4b, 0xdd, 0x5e, 0x97, 0x56, 0x2d, 0x56, 0x45, 0xdc, 0x5a,
    0x5a, 0x97, 0x5a, 0xeb, 0x82, 0xc5, 0x56, 0xdb, 0xda, 0xd6, 0xf6, 0xed,
    0x6b, 0x37, 0xdf, 0x7e, 0xb5, 0xa5, 0xad, 0x76, 0x7b, 0xb5, 0xf5, 0x7d,
    0x5f, 0xfb, 0x75, 0xf1, 0xfb, 0x9f, 0x7b, 0xef, 0x33, 0xf3, 0x64, 0x01,
    0xda, 0xdf, 0xef, 0x8b, 0xfe, 0xe7, 0x9c, 0x7b, 0xee, 0xb9, 0xfb, 0xb9,
    0xe7, 0x9e, 0xfb, 0xcc, 0xc3, 0x8c, 0xf9, 0x0f, 0x64, 0xd4, 0x36, 0xac,
    0xa2, 0x3b, 0x1e, 0xf0, 0x6c, 0xbe, 0xfe, 0xe0, 0xfd, 0x83, 0x8d, 0xef,
    0x45, 0xdf, 0x7b, 0xe8, 0xca, 0xbd, 0xfb, 0x9e, 0xef, 0x2e, 0x7a, 0xb7,
    0xec, 0xce, 0xde, 0xcf, 0xbd, 0x7f, 0x01, 0x51, 0x84, 0x88, 0x0e, 0x0c,
    0x37, 0xba, 0x48, 0xff, 0x55, 0x40, 0xb6, 0x51, 0x28, 0xf9, 0x30, 0x70,
    0x97, 0x83, 0xc8, 0x07, 0xfa, 0x5e, 0x0a, 0x51, 0x1b, 0xe8, 0xa1, 0x4c,
    0xa2, 0x57, 0x40, 0x2f, 0x9a, 0x43, 0x94, 0x03, 0x7a, 0x24, 0x97, 0xa8,
    0xb9, 0x8b, 0xe8, 0xc4, 0x3c, 0xa2, 0xf8, 0x4a, 0xa2, 0x7d, 0xc0, 0x41,
    0xe0, 0x23, 0xc0, 0xe5, 0xc0, 0x55, 0xc0, 0xc3, 0xc0, 0x13, 0xc0, 0xb3,
    0xc0, 0x09, 0xe0, 0x6b, 0xc0, 0xb7, 0x80, 0xef, 0x01, 0xbf, 0x01, 0x96,
    0xae, 0x22, 0x0a, 0x02, 0x87, 0x80, 0x5b, 0x81, 0xa3, 0xc0, 0x43, 0xc0,
    0x63, 0xc0, 0x17, 0x80, 0x27, 0x81, 0x67, 0x80, 0x9f, 0x02, 0x46, 0x33,
    0x51, 0x31, 0x50, 0x07, 0x34, 0x02, 0xab, 0x81, 0xcd, 0x80, 0x0f, 0x98,
    0x04, 0xae, 0x04, 0xee, 0x04, 0xee, 0x02, 0x8e, 0x00, 0x0f, 0x00, 0x8f,
    0x00, 0x5f, 0x00, 0x9e, 0x02, 0x9e, 0x01, 0x5e, 0x00, 0x5e, 0x02, 0xbe,
    0x09, 0xbc, 0x03, 0xcc, 0x6b, 0xc1, 0x18, 0x80, 0x51, 0xe0, 0x1a, 0xe0,
    0x09, 0xe0, 0xc7, 0x80, 0x7d, 0x35, 0xfa, 0x06, 0x6c, 0x00, 0xf6, 0x03,
    0x77, 0x01, 0x5f, 0x02, 0x7e, 0x04, 0xa4, 0xb4, 0x12, 0x2d, 0x07, 0xb6,
    0x00, 0x87, 0x80, 0x2f, 0x03, 0x3f, 0x05, 0x78, 0x92, 0xca, 0x80, 0x73,
    0x00, 0x2f, 0xb0, 0x07, 0xb8, 0x12, 0xb8, 0x1d, 0xf8, 0x36, 0x20, 0xd6,
    0xa0, 0xdf, 0xc0, 0x38, 0xf0, 0x69, 0xe0, 0xbb, 0xc0, 0xdf, 0x81, 0x0d,
    0x6b, 0xa1, 0x03, 0xfc, 0x14, 0xa8, 0x3c, 0x87, 0x28, 0x0c, 0x7c, 0x16,
    0x78, 0x1d, 0xa0, 0x76, 0xa2, 0x16, 0x60, 0x1b, 0xf0, 0x51, 0xe0, 0x1e,
    0xe0, 0x45, 0xe0, 0x37, 0x40, 0x4a, 0x07, 0x91, 0x07, 0x68, 0x01, 0xfa,
    0x80, 0x51, 0xe0, 0x1a, 0xe0, 0x1e, 0xe0, 0x04, 0xf0, 0x03, 0xe0, 0x2d,
    0xc0, 0xd6, 0x49, 0x54, 0x04, 0xb4, 0x00, 0x17, 0x00, 0x61, 0xe0, 0xe3,
    0xc0, 0xa7, 0x80, 0x2f, 0x02, 0x2f, 0x03, 0x6f, 0x00, 0xef, 0x02, 0x29,
    0x58, 0xcf, 0x85, 0x40, 0x35, 0xd0, 0x0e, 0x0c, 0x01, 0xbb, 0x80, 0xfd,
    0xc0, 0x27, 0x80, 0x7b, 0x81, 0xaf, 0x02, 0xbf, 0x05, 0xb2, 0xba, 0x31,
    0x0f, 0xc0, 0x06, 0x60, 0x3b, 0x70, 0x04, 0x78, 0x1d, 0xf8, 0x2d, 0xf0,
    0x0f, 0x20, 0xad, 0x07, 0x76, 0x02, 0x34, 0x03, 0x9b, 0x81, 0xed, 0xc0,
    0x3e, 0xe0, 0x20, 0xf0, 0x11, 0xe0, 0x0a, 0xe0, 0x30, 0x70, 0x1d, 0x70,
    0x2b, 0x70, 0x07, 0x70, 0x37, 0x70, 0x1f, 0xf0, 0x30, 0x70, 0x1c, 0x78,
    0x12, 0x78, 0x06, 0x78, 0x11, 0xf8, 0x06, 0xf0, 0x5d, 0xe0, 0x75, 0xe0,
    0x27, 0xc0, 0x7f, 0x02, 0x6f, 0x02, 0xbf, 0x02, 0xde, 0x06, 0xe6, 0xac,
    0xc3, 0x12, 0x00, 0x3b, 0x81, 0x03, 0xc0, 0xf5, 0xc0, 0x9d, 0xc0, 0x03,
    0xc0, 0x17, 0x80, 0xe7, 0x81, 0x93, 0xc0, 0x6b, 0xc0, 0x1b, 0xc0, 0x29,
    0xe0, 0x77, 0xc0, 0x9f, 0x81, 0x7f, 0x00, 0x45, 0xe7, 0x12, 0xad, 0x00,
    0x3a, 0x01, 0x2f, 0xb0, 0x0b, 0x98, 0x00, 0xf6, 0x03, 0x97, 0x01, 0x1f,
    0x07, 0xae, 0x07, 0x6e, 0x03, 0xee, 0x03, 0x1e, 0x03, 0x9e, 0x01, 0x9e,
    0x03, 0xbe, 0x03, 0xbc, 0x0e, 0xfc, 0x06, 0x78, 0x17, 0xf8, 0x3b, 0x60,
    0xac, 0x27, 0xca, 0x00, 0xf2, 0x80, 0xf9, 0x40, 0x31, 0x50, 0x06, 0x0c,
    0x02, 0xdb, 0x81, 0x43, 0xc0, 0xbd, 0xc0, 0x63, 0xc0, 0xe3, 0xc0, 0xd3,
    0xc0, 0x4b, 0xc0, 0x6b, 0xc0, 0xef, 0x81, 0xf4, 0x5e, 0x22, 0x37, 0xd0,
    0x04, 0xf4, 0x02, 0xa3, 0xc0, 0x25, 0xc0, 0x55, 0xc0, 0xed, 0xc0, 0xe3,
    0xc0, 0xcb, 0xc0, 0xf7, 0x81, 0x9f, 0x01, 0xff, 0x03, 0x64, 0x6f, 0x80,
    0x0d, 0x01, 0xf5, 0xc0, 0x76, 0x60, 0x0c, 0xd8, 0x0b, 0xc4, 0x81, 0xab,
    0x81, 0xcf, 0x02, 0x8f, 0x00, 0x5f, 0x06, 0xbe, 0x0d, 0xbc, 0x01, 0xbc,
    0x0d, 0xfc, 0x19, 0x30, 0x36, 0x12, 0xe5, 0x02, 0xc5, 0x40, 0x2d, 0x70,
    0x2e, 0xd0, 0x0f, 0x8c, 0x02, 0x07, 0x80, 0xab, 0x81, 0x1b, 0x81, 0xdb,
    0x80, 0x23, 0xc0, 0x51, 0xe0, 0x61, 0xe0, 0x38, 0xf0, 0x0c, 0xf0, 0x12,
    0xf0, 0x3d, 0xe0, 0x27, 0xc0, 0x29, 0xe0, 0xf7, 0xc0, 0xda, 0x3e, 0xa2,
    0x9b, 0x80, 0xcf, 0x00, 0xc7, 0x80, 0xa7, 0x80, 0x67, 0x81, 0xef, 0x02,
    0x6f, 0x00, 0xbf, 0x00, 0xde, 0x06, 0x8c, 0x4d, 0xb0, 0x17, 0x60, 0x21,
    0xd0, 0x04, 0x74, 0x03, 0x5e, 0x60, 0x2f, 0x70, 0x29, 0xf0, 0x31, 0xe0,
    0x16, 0xe0, 0x5e, 0xe0, 0x21, 0xe0, 0x79, 0xe0, 0x9b, 0xc0, 0x8f, 0x81,
    0x3f, 0x01, 0x59, 0x9b, 0x51, 0x16, 0x58, 0x0a, 0xd4, 0x03, 0x2d, 0xc0,
    0x66, 0x60, 0x0f, 0x70, 0x25, 0x70, 0x03, 0x70, 0x3b, 0x70, 0x17, 0x70,
    0x14, 0x78, 0x1e, 0xf8, 0x3a, 0xf0, 0x7d, 0xe0, 0x0f, 0xc0, 0x9f, 0x80,
    0xbf, 0x02, 0x8e, 0x2d, 0x44, 0x99, 0xc0, 0x52, 0xa0, 0x09, 0xe8, 0x06,
    0x7a, 0x81, 0x6d, 0xc0, 0x6e, 0x60, 0x3f, 0x70, 0x29, 0x70, 0x25, 0x70,
    0x03, 0x70, 0x37, 0x70, 0x1f, 0xf0, 0x18, 0xf0, 0x24, 0xf0, 0x02, 0xf0,
    0x4d, 0xe0, 0x67, 0xc0, 0x29, 0xe0, 0xaf, 0x40, 0x7e, 0x3f, 0xe6, 0x13,
    0x58, 0x05, 0xb4, 0x03, 0xec, 0x70, 0x17, 0x00, 0x2b, 0x80, 0x2a, 0xa0,
    0x1a, 0xa8, 0x01, 0x6a, 0x81, 0x3a, 0xa0, 0x1e, 0x68, 0x00, 0x1a, 0x81,
    0x26, 0x00, 0x6e, 0x92, 0xe0, 0xfe, 0x08, 0xee, 0x8a, 0xe0, 0x9a, 0x08,
    0x2e, 0x88, 0xb4, 0xbb, 0x21, 0xb8, 0x0f, 0x82, 0xdb, 0x20, 0xed, 0x2a,
    0x08, 0xdb, 0x9d, 0xb0, 0x8d, 0x09, 0xdb, 0x93, 0xb0, 0x05, 0x09, 0xdb,
    0x82, 0x60, 0xda, 0x04, 0x73, 0x24, 0x98, 0x14, 0xc1, 0x44, 0x08, 0x4b,
    0x4d, 0x58, 0x1a, 0xc2, 0xb4, 0x13, 0xa6, 0x8e, 0x30, 0x6c, 0x42, 0x37,
    0x09, 0x5d, 0xa3, 0xf3, 0x80, 0x01, 0x60, 0x10, 0xf0, 0x02, 0x43, 0xfa,
    0x2c, 0x38, 0x1f, 0xc0, 0xf1, 0x40, 0x5b, 0x81, 0x6d, 0xc0, 0x08, 0x70,
    0x21, 0xb0, 0x1d, 0xf8, 0x10, 0xb0, 0x83, 0xcf, 0x05, 0x52, 0xe7, 0xc5,
    0x4e, 0x60, 0x14, 0x18, 0x03, 0xfc, 0xc0, 0x2e, 0x60, 0x37, 0x30, 0x0e,
    0xec, 0x01, 0x82, 0xc0, 0x04, 0x10, 0x22, 0x75, 0xd6, 0x44, 0x81, 0x18,
    0x30, 0x09, 0xec, 0xe3, 0x33, 0x09, 0x38, 0x08, 0x5c, 0x02, 0x7c, 0x18,
    0xb8, 0x14, 0xf8, 0x08, 0xf0, 0x09, 0x3e, 0x93, 0x80, 0xcf, 0xf0, 0xd9,
    0x03, 0xdc, 0x0b, 0x7c, 0x16, 0xb8, 0x0f, 0xb8, 0x9f, 0xd4, 0xbc, 0x9a,
    0x7f, 0x79, 0x9a, 0x1e, 0xc1, 0x24, 0xce, 0xd3, 0xfc, 0x31, 0xf0, 0xf9,
    0x9a, 0x3f, 0x6e, 0x91, 0x3f, 0x0d, 0xde, 0x03, 0x6a, 0xe8, 0x74, 0x85,
    0xe6, 0x4f, 0x68, 0xb9, 0xcd, 0x22, 0x67, 0xfe, 0x95, 0xc6, 0x24, 0x7f,
    0x4c, 0xeb, 0xd8, 0xb5, 0xce, 0x72, 0xcd, 0xbf, 0xa6, 0xe5, 0xa9, 0x96,
    0xb2, 0xe9, 0xc0, 0x1b, 0x5a, 0x9e, 0xa1, 0xe5, 0x65, 0x40, 0x96, 0xee,
    0x27, 0xcb, 0xe7, 0x68, 0x39, 0xf3, 0x73, 0x2d, 0x7c, 0x8e, 0x45, 0xdf,
    0xa5, 0xf5, 0x99, 0xcf, 0xb7, 0x94, 0x9d, 0x6f, 0x69, 0x6b, 0x81, 0xee,
    0x1b, 0xf3, 0x8b, 0xf4, 0x58, 0xca, 0x75, 0xf9, 0x53, 0xe0, 0x17, 0x6b,
    0xfe, 0xf7, 0xe0, 0x97, 0x68, 0xfe, 0x7d, 0xf0, 0x4b, 0x35, 0x6f, 0x6f,
    0x4a, 0xf2, 0x59, 0x4d, 0x8a, 0x2e, 0xd1, 0x6d, 0x55, 0x68, 0xde, 0xac,
    0xbf, 0xc2, 0xc2, 0x57, 0x5a, 0xe6, 0x6d, 0xb9, 0xa5, 0xff, 0x4d, 0x96,
    0xfe, 0x37, 0x5b, 0xfa, 0xbc, 0x5a, 0xcb, 0x97, 0x6a, 0x3e, 0xaf, 0x49,
    0xd5, 0xc3, 0xfc, 0x42, 0xf0, 0xcb, 0x34, 0x5f, 0x6e, 0x91, 0x9b, 0x73,
    0xde, 0x6a, 0x19, 0x6f, 0xab, 0xa5, 0x0f, 0x6d, 0x16, 0x9d, 0x6e, 0x4b,
    0x1f, 0x7a, 0x2c, 0xfa, 0xcc, 0xaf, 0x68, 0x4a, 0xf2, 0xa6, 0x0d, 0x9c,
    0x6b, 0xe9, 0xe7, 0xb9, 0xba, 0x9f, 0x95, 0x9a, 0x6f, 0x6c, 0x52, 0x3a,
    0x1b, 0xb5, 0x0e, 0xdb, 0xe3, 0x66, 0xcd, 0x5f, 0xab, 0x79, 0xd6, 0xbf,
    0x4e, 0xf3, 0x6d, 0xd0, 0xbf, 0x5e, 0xf3, 0xdd, 0xe0, 0x6f, 0xd0, 0x7c,
    0x3f, 0xf8, 0x4f, 0x6a, 0xfe, 0x02, 0xf0, 0x37, 0x6a, 0x7e, 0x1c, 0xfc,
    0x9d, 0x9a, 0x8f, 0x80, 0xbf, 0x59, 0xf3, 0x97, 0x5a, 0x74, 0x0e, 0x5b,
    0x78, 0x9e, 0x67, 0x93, 0xbf, 0xd1, 0x22, 0x67, 0xfb, 0xbc, 0x49, 0xf3,
    0x77, 0x58, 0xea, 0x3c, 0x62, 0xd1, 0x79, 0x14, 0xfc, 0x6d, 0x9a, 0x7f,
    0xc2, 0x22, 0x7f, 0xd6, 0xc2, 0xbf, 0x64, 0xe1, 0x5f, 0xb5, 0xf0, 0x3f,
    0x68, 0x4a, 0xd6, 0xff, 0x06, 0xf8, 0x5b, 0x35, 0x7f, 0x0a, 0xfc, 0xa7,
    0x34, 0xff, 0x0e, 0xf8, 0xdb, 0x35, 0xff, 0x37, 0xf0, 0x77, 0x68, 0x3e,
    0x6d, 0x65, 0xb2, 0xdd, 0xc2, 0x95, 0xc9, 0xbe, 0xb9, 0x57, 0x26, 0xeb,
    0x5f, 0xb1, 0x32, 0x59, 0x7f, 0xa3, 0x45, 0xbe, 0xd0, 0xd2, 0x87, 0x63,
    0x96, 0xb1, 0xb7, 0x59, 0x74, 0xba, 0x2d, 0x75, 0xf6, 0x81, 0xff, 0x37,
    0x73, 0x9e, 0xc1, 0x7f, 0x5a, 0xf3, 0x17, 0x81, 0xbf, 0xc5, 0x9c, 0xf3,
    0x95, 0xca, 0xa7, 0x6c, 0xd1, 0xeb, 0x78, 0xb7, 0xe6, 0x79, 0x1d, 0xef,
    0xd1, 0x3c, 0xaf, 0x8b, 0xc9, 0x2f, 0xb4, 0xf0, 0xa6, 0x8d, 0x0d, 0x58,
    0x6c, 0x6c, 0x50, 0xf3, 0x85, 0xc0, 0xc7, 0x88, 0xf7, 0xde, 0x5c, 0x7a,
    0x44, 0xd2, 0x56, 0x3a, 0x2e, 0x69, 0x17, 0x7d, 0x51, 0xd2, 0x54, 0xaa,
    0x45, 0xac, 0x5d, 0x8c, 0x53, 0xe0, 0x29, 0x62, 0x0a, 0xbf, 0x8d, 0x74,
    0x09, 0x76, 0xfa, 0x3f, 0x88, 0xa9, 0x4a, 0xbb, 0x41, 0x5f, 0xe0, 0xf9,
    0x81, 0x87, 0x7f, 0x89, 0x54, 0xfa, 0xef, 0x92, 0xb6, 0x52, 0xba, 0x50,
    0x74, 0x8e, 0xa6, 0x15, 0x9a, 0x36, 0x0a, 0xa5, 0xbf, 0x5a, 0x97, 0x37,
    0xeb, 0xe9, 0x06, 0x2d, 0x85, 0xc7, 0x78, 0x98, 0x98, 0x22, 0x8e, 0x93,
    0x74, 0x0b, 0x09, 0x29, 0xaf, 0xa4, 0x32, 0x49, 0x97, 0xd1, 0x32, 0xa1,
    0xf2, 0xb9, 0x9c, 0x07, 0xde, 0xe6, 0x61, 0x3d, 0xb6, 0xaf, 0x4a, 0xaa,
    0xf4, 0x3c, 0x3a, 0xbf, 0x1c, 0xe9, 0x97, 0x49, 0xf9, 0x96, 0x57, 0x24,
    0x15, 0xf4, 0x0d, 0x62, 0xff, 0xa2, 0xe4, 0x8b, 0xb5, 0x7c, 0x09, 0x65,
    0xd3, 0x09, 0x49, 0xed, 0xf4, 0x5d, 0x49, 0x2b, 0xa9, 0x5d, 0x28, 0x3f,
    0xc2, 0xf5, 0x2c, 0xd5, 0xf5, 0x2e, 0xd5, 0xf2, 0x0a, 0xe4, 0xb0, 0x7e,
    0x85, 0x96, 0x57, 0x68, 0x79, 0xa5, 0x9e, 0xcf, 0x4a, 0x9c, 0x72, 0x4f,
    0x48, 0x7a, 0x0e, 0xbd, 0x28, 0xe9, 0x06, 0xfa, 0xba, 0xa4, 0xf3, 0x28,
    0x4d, 0x28, 0x79, 0x96, 0x50, 0x7a, 0xa5, 0x9a, 0x7a, 0x34, 0xed, 0x12,
    0xec, 0x57, 0x04, 0xfd, 0x8d, 0x94, 0x8f, 0xfa, 0xaa, 0xa6, 0x4b, 0x85,
    0xa2, 0x3c, 0x4f, 0x7c, 0x2e, 0x5f, 0x4d, 0x8a, 0x1e, 0x96, 0x74, 0x11,
    0x7d, 0x41, 0xd2, 0x4a, 0xca, 0x14, 0x7c, 0x3e, 0x64, 0xd1, 0x8f, 0x89,
    0xa9, 0x83, 0x7e, 0x2a, 0x69, 0x3a, 0xbd, 0x4b, 0x7c, 0x26, 0x64, 0xd2,
    0x57, 0x24, 0xad, 0xa4, 0xef, 0x6b, 0xfa, 0x5b, 0xe2, 0x33, 0xa1, 0x91,
    0xbe, 0xac, 0xe9, 0x49, 0x49, 0x53, 0xe9, 0x67, 0x92, 0x8e, 0x50, 0x35,
    0xea, 0x73, 0x40, 0xce, 0x67, 0x45, 0x0a, 0xb8, 0xef, 0x11, 0x9f, 0x1b,
    0xc3, 0xd4, 0x26, 0x98, 0xae, 0xa1, 0x73, 0x40, 0xd3, 0x74, 0x7e, 0x7a,
    0x82, 0xce, 0xa5, 0x27, 0x25, 0x9d, 0x43, 0x2d, 0xc8, 0xcf, 0xd4, 0xf5,
    0x65, 0xe9, 0xfc, 0x2c, 0x72, 0xca, 0xfc, 0x2c, 0xcc, 0xfb, 0x2a, 0xa1,
    0x68, 0xb3, 0xe0, 0x33, 0x25, 0x87, 0xfe, 0x83, 0x98, 0x56, 0xd0, 0x9f,
    0x41, 0xb3, 0x75, 0xbf, 0xb2, 0xb1, 0xce, 0xbf, 0x91, 0x34, 0x97, 0x16,
    0x09, 0xa6, 0x79, 0xe4, 0x06, 0x75, 0xea, 0xfe, 0x3b, 0x81, 0x3f, 0x68,
    0xfa, 0xdf, 0xa4, 0xce, 0xa3, 0xff, 0xd4, 0xf4, 0xd7, 0x9a, 0xbe, 0xa5,
    0xe9, 0xef, 0x25, 0xdd, 0x41, 0xff, 0x25, 0xe9, 0x3c, 0x7a, 0x47, 0xcb,
    0xb9, 0x9c, 0x4b, 0xb7, 0xe7, 0xd2, 0xf5, 0xb9, 0x10, 0x51, 0xd4, 0xa3,
    0x9d, 0x5c, 0xdd, 0xef, 0x5c, 0x44, 0x31, 0xb9, 0x42, 0xd1, 0x7c, 0xc1,
    0x67, 0xf4, 0x45, 0xf4, 0x7f, 0x88, 0x69, 0x1d, 0xfd, 0x5c, 0xd2, 0x56,
    0x5a, 0x20, 0xe5, 0x9d, 0xb4, 0x50, 0xd2, 0x55, 0xb4, 0x5c, 0xd2, 0x6e,
    0x4d, 0xbb, 0xe8, 0x5c, 0xc1, 0xe7, 0xa2, 0x6a, 0x27, 0x1f, 0xde, 0xfd,
    0x19, 0x4d, 0x4f, 0x92, 0x3a, 0x2f, 0xb9, 0xdd, 0x02, 0xdd, 0xde, 0x7c,
    0xcc, 0xc3, 0x6b, 0x92, 0x2e, 0xa3, 0x3f, 0x11, 0x9f, 0x9b, 0xf9, 0xf4,
    0x1d, 0xde, 0xe7, 0x58, 0xb9, 0xe7, 0xe5, 0x3e, 0x5d, 0x2b, 0xf5, 0x16,
    0x61, 0x85, 0x7f, 0x24, 0xe9, 0x32, 0x3a, 0x25, 0x69, 0x15, 0xbd, 0x2d,
    0xe9, 0x10, 0xe5, 0x08, 0xa6, 0x4d, 0x54, 0x28, 0xe9, 0x4a, 0x9a, 0x2f,
    0xe9, 0x87, 0x68, 0x89, 0xa4, 0x17, 0xd0, 0x1a, 0x49, 0x07, 0x69, 0x9d,
    0xdc, 0xef, 0xe7, 0xc9, 0xfa, 0x8a, 0xf5, 0x38, 0x8b, 0xf5, 0x38, 0x4b,
    0x10, 0xb9, 0xa5, 0xca, 0x7d, 0xad, 0xfa, 0x55, 0x8a, 0xc8, 0x4c, 0x51,
    0x35, 0x0e, 0xa6, 0xdf, 0x92, 0xb4, 0x94, 0xfe, 0x28, 0x29, 0xa2, 0x30,
    0xa1, 0xe4, 0x05, 0x92, 0x6e, 0xa5, 0x22, 0x9d, 0x6e, 0xd5, 0x74, 0xad,
    0xdc, 0xa7, 0x7d, 0xb2, 0x1e, 0x8f, 0xae, 0xc7, 0xa3, 0xeb, 0xf1, 0xe8,
    0x7a, 0x3c, 0xba, 0x1e, 0x8f, 0x2e, 0xef, 0xd1, 0xe5, 0x3d, 0xba, 0x7c,
    0x99, 0xee, 0x4f, 0x99, 0x2e, 0x5f, 0xa6, 0xcb, 0x95, 0xe9, 0x72, 0x65,
    0x5a, 0xbf, 0x4c, 0xeb, 0x97, 0x63, 0x1f, 0xa6, 0x4a, 0x3f, 0xb0, 0x84,
    0x1e, 0x93, 0xfb, 0xbf, 0x57, 0xa6, 0x17, 0xeb, 0xf4, 0x12, 0x9c, 0x9e,
    0x9c, 0x5e, 0x0a, 0xef, 0xab, 0x68, 0x1b, 0xb9, 0xe4, 0xfe, 0xde, 0x22,
    0xd3, 0x15, 0xa8, 0xf7, 0x4b, 0x7a, 0xdf, 0xbf, 0x2e, 0x69, 0x06, 0xfd,
    0x92, 0xd4, 0x39, 0xfd, 0x2b, 0x49, 0x1b, 0xa9, 0x46, 0xee, 0x63, 0xb5,
    0x2e, 0x95, 0xd8, 0x19, 0x0f, 0x48, 0xaa, 0xc6, 0x55, 0x89, 0x19, 0x7d,
    0x55, 0xd2, 0x12, 0xfa, 0xb6, 0xce, 0xff, 0xa1, 0xa4, 0x6a, 0xfd, 0x2a,
    0xb1, 0x12, 0xbf, 0x90, 0x54, 0xd0, 0x7b, 0x92, 0x16, 0x91, 0x43, 0xd6,
    0xb7, 0x80, 0x52, 0x24, 0x5d, 0x48, 0xa9, 0x92, 0xae, 0x93, 0xeb, 0xc3,
    0x7e, 0x24, 0x4f, 0xd2, 0xf5, 0x34, 0x4f, 0x52, 0xb5, 0x5e, 0x95, 0xb0,
    0x98, 0x62, 0x49, 0x0b, 0xa9, 0x44, 0xd2, 0x0b, 0xa9, 0x5c, 0xd2, 0xd5,
    0xb4, 0x58, 0xa8, 0xfe, 0x2c, 0x97, 0xb4, 0x9f, 0xaa, 0x24, 0x1d, 0xa4,
    0x1e, 0xe9, 0x77, 0x9a, 0x65, 0xbf, 0x97, 0x21, 0x92, 0x7f, 0x5f, 0x52,
    0x35, 0xef, 0xcb, 0xf5, 0x3c, 0x2f, 0x47, 0x44, 0xfd, 0x35, 0xed, 0x8f,
    0xfe, 0x4a, 0x7c, 0x47, 0x50, 0xf2, 0x15, 0xba, 0x3f, 0x2b, 0x74, 0xfb,
    0x55, 0x90, 0xa7, 0x90, 0xa2, 0xa9, 0x92, 0x36, 0xd1, 0x51, 0x49, 0xcf,
    0xa5, 0x07, 0x25, 0x2d, 0xa0, 0x7f, 0x97, 0xd4, 0x4d, 0x3f, 0x90, 0x34,
    0x85, 0xde, 0x90, 0xb4, 0x5e, 0xee, 0xa3, 0x2a, 0xdc, 0x3a, 0xde, 0xd4,
    0xfa, 0xff, 0xa3, 0xcb, 0xff, 0x2f, 0xa9, 0xd8, 0xec, 0x03, 0x49, 0xcb,
    0xa8, 0x57, 0x70, 0xbc, 0xa5, 0xea, 0x5f, 0xad, 0xe7, 0x77, 0x35, 0x2c,
    0xe3, 0x27, 0x92, 0x56, 0x52, 0x87, 0xe0, 0xb8, 0x4b, 0xf5, 0xb7, 0x15,
    0xeb, 0x66, 0x08, 0x8e, 0xbd, 0x06, 0xa5, 0x7e, 0x1b, 0x46, 0xf0, 0xa8,
    0xa4, 0x4a, 0x6f, 0x2d, 0x4e, 0x49, 0x96, 0xaf, 0x85, 0x65, 0xf0, 0x7e,
    0x3b, 0x47, 0xd7, 0x7b, 0x0e, 0x6e, 0x19, 0x8a, 0x6e, 0xa7, 0x87, 0xb4,
    0xfc, 0x69, 0x9d, 0x7e, 0x56, 0xd2, 0x15, 0x64, 0x13, 0x2a, 0xbd, 0x42,
    0xd2, 0xc5, 0xb4, 0x52, 0xf0, 0x5d, 0xa7, 0x86, 0x3e, 0x47, 0x7c, 0xd7,
    0x51, 0xf5, 0x74, 0xea, 0x72, 0x9d, 0xd0, 0x7b, 0x4e, 0xd2, 0x25, 0xb2,
    0x9d, 0x4e, 0x58, 0xd8, 0xef, 0x24, 0xad, 0x25, 0xbb, 0x50, 0xe9, 0x4a,
    0xa1, 0xf2, 0xb9, 0xbe, 0x2e, 0x5d, 0xbe, 0x4b, 0xf7, 0xa3, 0x4b, 0xf7,
    0xa3, 0x4b, 0xb7, 0xd7, 0x8d, 0x71, 0xfd, 0x85, 0x98, 0x96, 0x93, 0x53,
    0x70, 0xac, 0xa8, 0xfa, 0xb3, 0x0e, 0x9e, 0xfb, 0xff, 0x12, 0xc7, 0x85,
    0x2a, 0xbd, 0x5e, 0xd7, 0xb3, 0x1e, 0x37, 0xa5, 0x3a, 0xc1, 0xf7, 0x2c,
    0x95, 0xee, 0xd5, 0xf6, 0xdc, 0x87, 0x1b, 0x5d, 0xb6, 0xe0, 0xfb, 0x96,
    0xda, 0x07, 0x1c, 0x93, 0x23, 0x09, 0xeb, 0x54, 0x7f, 0x7c, 0xde, 0x1e,
    0xc6, 0x45, 0xeb, 0x35, 0x04, 0x13, 0xab, 0x87, 0x54, 0x3c, 0x20, 0xa4,
    0x3f, 0x4c, 0xe6, 0x1f, 0x47, 0x7e, 0x8a, 0x0e, 0x30, 0x4a, 0x74, 0xfe,
    0x6a, 0x4b, 0xfe, 0x09, 0xe4, 0x2f, 0xd4, 0xf9, 0xee, 0x59, 0xf2, 0x5f,
    0x43, 0xfe, 0x32, 0x9d, 0xcf, 0xf1, 0xad, 0xb0, 0xb3, 0x1f, 0x4c, 0xe6,
    0xbf, 0x83, 0xfc, 0xf5, 0x83, 0xc9, 0xb4, 0xd0, 0x7a, 0x66, 0x9a, 0x2f,
    0x81, 0xbb, 0x74, 0xbe, 0x67, 0x96, 0x7c, 0x37, 0xf2, 0xef, 0xd2, 0xf9,
    0x1c, 0x0f, 0xe4, 0x38, 0x55, 0xfb, 0x37, 0x36, 0xf2, 0x79, 0xa2, 0xfe,
    0xbe, 0xa9, 0xf3, 0x17, 0x9f, 0x26, 0xff, 0x4d, 0x9d, 0xbf, 0x44, 0xa7,
    0xad, 0xfd, 0x6f, 0x46, 0xfd, 0xff, 0xad, 0xf3, 0x97, 0x6a, 0xb9, 0xc3,
    0x92, 0xdf, 0x8f, 0xfc, 0x34, 0xaf, 0x4a, 0x57, 0x68, 0x79, 0xaf, 0x25,
    0x7f, 0x1c, 0xf9, 0x0b, 0x74, 0xfe, 0x8a, 0x59, 0xe6, 0xe7, 0x10, 0xf2,
    0xdb, 0x75, 0x7e, 0xa5, 0xce, 0x67, 0xbd, 0x3b, 0x1a, 0x55, 0xfe, 0x8d,
    0xc8, 0xf7, 0xea, 0xfc, 0x65, 0x3a, 0xdf, 0x3a, 0x7f, 0x3f, 0x47, 0xfe,
    0xb7, 0x74, 0xfe, 0xf2, 0x59, 0xea, 0x7f, 0x1f, 0xf9, 0x6f, 0xea, 0xfc,
    0xaa, 0x59, 0xf2, 0xd3, 0x70, 0xc1, 0xfe, 0x2b, 0xf2, 0xd7, 0x0c, 0x71,
    0xdc, 0x60, 0xc8, 0x3b, 0xe8, 0x45, 0x75, 0x6a, 0x2d, 0xbd, 0x30, 0xbe,
    0x8b, 0x9d, 0x1c, 0x39, 0x67, 0x51, 0xc8, 0x6d, 0x40, 0x96, 0x65, 0xb4,
    0x18, 0x99, 0x54, 0x64, 0x2c, 0x84, 0x07, 0xfb, 0x26, 0x5d, 0xec, 0x6e,
    0x83, 0xbe, 0xcb, 0xc8, 0x36, 0x94, 0xe6, 0xbf, 0x69, 0xcd, 0x2b, 0xa0,
    0x99, 0x01, 0xa9, 0x59, 0xdf, 0x78, 0x1d, 0xc7, 0x0a, 0x66, 0x7d, 0xa6,
    0xd6, 0x3e, 0xad, 0x95, 0x6c, 0x37, 0x54, 0xa7, 0xd6, 0x64, 0xb6, 0x76,
    0x43, 0x4e, 0x07, 0xc7, 0x3f, 0x46, 0x8e, 0xd4, 0x17, 0x52, 0x7f, 0xb2,
    0x4e, 0xd9, 0xb2, 0x87, 0x77, 0x74, 0xa9, 0x8d, 0x22, 0xee, 0x87, 0x10,
    0x2b, 0xe4, 0xc8, 0xfa, 0x78, 0x9c, 0x97, 0xd4, 0xa9, 0xbb, 0x6d, 0xc4,
    0x39, 0x17, 0x69, 0x0f, 0xf6, 0x57, 0xc4, 0xc9, 0xe7, 0xc9, 0x88, 0xd3,
    0x46, 0x23, 0x38, 0x00, 0xf8, 0xae, 0x6b, 0x87, 0x2e, 0xdf, 0xa9, 0x0f,
    0xd7, 0xa9, 0x73, 0x3a, 0xea, 0x7c, 0x01, 0xe9, 0x2c, 0x5b, 0xd4, 0xf9,
    0x1c, 0x68, 0x06, 0x7c, 0xd2, 0x1c, 0xc8, 0x9e, 0x67, 0x19, 0xec, 0x3f,
    0x93, 0x5c, 0x39, 0xa1, 0xda, 0x2e, 0x44, 0x10, 0x15, 0xef, 0xcc, 0xd5,
    0xbd, 0x56, 0x7f, 0xdc, 0xde, 0x5c, 0x69, 0x1b, 0x36, 0x29, 0xbd, 0xa9,
    0x4e, 0x3d, 0x43, 0x71, 0x39, 0xeb, 0x6d, 0x0e, 0x72, 0xb9, 0x1b, 0x6c,
    0x39, 0x14, 0x72, 0xba, 0xd0, 0x5e, 0x16, 0xea, 0xcc, 0x94, 0xfd, 0x0d,
    0x39, 0xed, 0x7a, 0x8c, 0x5f, 0x41, 0x79, 0x57, 0x7b, 0x83, 0xad, 0x84,
    0x8a, 0x6c, 0x3c, 0xb7, 0x1f, 0x97, 0x73, 0x6b, 0x33, 0x4b, 0xd8, 0x9a,
    0x6d, 0x2e, 0xf2, 0x96, 0xab, 0x12, 0x36, 0x59, 0xe2, 0x59, 0xc8, 0x75,
    0xca, 0x16, 0x72, 0xaf, 0x83, 0x87, 0x9d, 0x0b, 0x6f, 0x6b, 0xc8, 0xe7,
    0x00, 0x77, 0xd7, 0xa9, 0x67, 0x27, 0xde, 0xf6, 0xe4, 0x3c, 0x16, 0x89,
    0x85, 0xf0, 0xfe, 0x69, 0x54, 0xe4, 0x98, 0x2b, 0xeb, 0x3e, 0x0f, 0xfa,
    0x21, 0x0e, 0xac, 0x90, 0xa7, 0xe4, 0x73, 0x12, 0xf2, 0x66, 0xc7, 0x2a,
    0xf2, 0x38, 0xd2, 0x31, 0x5b, 0xd9, 0x18, 0x4f, 0x91, 0x70, 0xa0, 0x96,
    0x56, 0xf4, 0x30, 0x4b, 0x84, 0xdc, 0x1c, 0x5b, 0x79, 0x44, 0x16, 0xf2,
    0x72, 0xb9, 0xcf, 0xae, 0x90, 0x7b, 0x1e, 0xec, 0x28, 0xcb, 0x70, 0xa1,
    0x17, 0xf9, 0xe8, 0x05, 0xb8, 0x32, 0xc5, 0x85, 0x9c, 0x4e, 0x94, 0xce,
    0x72, 0x94, 0x3b, 0xcf, 0x93, 0xf4, 0x62, 0xe7, 0x71, 0x49, 0x43, 0xce,
    0x3c, 0x55, 0x57, 0x7b, 0x16, 0xf5, 0x77, 0xa0, 0x17, 0xee, 0x39, 0xf0,
    0xac, 0x3c, 0x77, 0xa6, 0x1d, 0x7c, 0xa1, 0x4e, 0x3d, 0xab, 0x98, 0x6a,
    0x2f, 0x1f, 0x87, 0x1d, 0xcc, 0x85, 0x8e, 0x4d, 0xce, 0xf5, 0x97, 0xf4,
    0xda, 0x47, 0x9c, 0xbc, 0x2b, 0x46, 0xd0, 0xc7, 0xad, 0x46, 0x0a, 0xf2,
    0x1d, 0x7a, 0x3d, 0x4f, 0xd4, 0xa9, 0xb8, 0x31, 0xec, 0x5e, 0xce, 0x7e,
    0xb6, 0xd6, 0x81, 0x5e, 0x97, 0xd0, 0x40, 0x7d, 0x0a, 0x45, 0x6a, 0x37,
    0x50, 0x87, 0x11, 0x72, 0xdf, 0x80, 0xfc, 0xb9, 0x89, 0x36, 0x5f, 0xae,
    0x53, 0xcf, 0x4a, 0xc2, 0xee, 0x15, 0x18, 0x8f, 0x17, 0xd6, 0x16, 0x72,
    0x7f, 0x52, 0xb6, 0x48, 0x89, 0x36, 0xbf, 0x93, 0x68, 0xf3, 0x6e, 0xd9,
    0x66, 0x2a, 0xda, 0x4c, 0x23, 0xb6, 0x2d, 0x21, 0xf3, 0x7f, 0x58, 0xa7,
    0x9e, 0xc3, 0xa8, 0xfc, 0xb9, 0xda, 0x0a, 0x71, 0x5f, 0xad, 0x53, 0xcf,
    0x6d, 0x86, 0x9c, 0xf3, 0xa4, 0x7d, 0xdb, 0xb4, 0xfe, 0x9b, 0x75, 0xea,
    0x79, 0x90, 0x07, 0x3b, 0x37, 0xe2, 0xe4, 0x9b, 0xfd, 0x40, 0x69, 0x1e,
    0x6d, 0x15, 0x4d, 0xb0, 0x8e, 0x5c, 0xcc, 0xdf, 0x2a, 0x68, 0x65, 0x88,
    0xad, 0x62, 0x25, 0xb9, 0x4a, 0xb7, 0x8a, 0x55, 0x32, 0x9e, 0x4d, 0x83,
    0x87, 0xe7, 0xba, 0xde, 0xd6, 0xf6, 0x1a, 0x71, 0x6e, 0x97, 0xb6, 0xbd,
    0x0c, 0xe3, 0xba, 0x9a, 0x2e, 0x4a, 0x1d, 0x71, 0x16, 0xc0, 0xb6, 0xf3,
    0x69, 0x24, 0x37, 0x97, 0xb6, 0xe6, 0xa1, 0xb6, 0x79, 0x85, 0xd8, 0xa1,
    0x17, 0xba, 0xe6, 0xc9, 0xb9, 0x11, 0xb2, 0x4f, 0x7f, 0xa9, 0x53, 0xbe,
    0xce, 0xdb, 0x9e, 0x8b, 0x38, 0xa8, 0x19, 0x51, 0x77, 0xc8, 0xb9, 0x45,
    0x5a, 0xa6, 0xb7, 0x93, 0xdb, 0x6d, 0x84, 0x56, 0x45, 0x86, 0xdc, 0xe5,
    0xb6, 0x02, 0x69, 0x15, 0xf5, 0x54, 0x61, 0xa4, 0x3a, 0xbd, 0x9d, 0xf9,
    0xd4, 0x6c, 0xa4, 0xc2, 0x7e, 0x79, 0x76, 0x3e, 0x06, 0xad, 0x91, 0x0e,
    0xb4, 0xd4, 0x91, 0x8b, 0xba, 0xd3, 0xa5, 0xed, 0x63, 0x5c, 0xa2, 0x31,
    0xdb, 0x9c, 0x0f, 0x47, 0xbd, 0x7a, 0x16, 0x15, 0x72, 0xf2, 0x13, 0xbf,
    0xb0, 0xf3, 0x1e, 0xbd, 0x47, 0x84, 0x9e, 0x17, 0xeb, 0xbe, 0x49, 0x91,
    0x91, 0xc5, 0x54, 0x99, 0xb9, 0xcf, 0x33, 0xeb, 0xd5, 0x3c, 0x79, 0x61,
    0x57, 0xcd, 0x98, 0x41, 0xaf, 0x3b, 0x57, 0xee, 0xa1, 0xa1, 0xd2, 0x79,
    0xb8, 0x87, 0xa4, 0x62, 0xb6, 0x0a, 0xd0, 0x9f, 0x26, 0xac, 0xa7, 0xcb,
    0x79, 0xa1, 0x7b, 0xde, 0x94, 0xb2, 0xae, 0x33, 0x94, 0x6d, 0x4e, 0x94,
    0x5d, 0xc9, 0x65, 0xc9, 0x2c, 0x6b, 0x58, 0xfa, 0x27, 0xeb, 0x40, 0x39,
    0x73, 0xcd, 0xe6, 0xd7, 0xcf, 0x5c, 0xb3, 0x22, 0xbd, 0x66, 0xf3, 0x31,
    0xd2, 0x8a, 0xe4, 0x9a, 0xe5, 0x98, 0x6b, 0xe6, 0x90, 0x33, 0x03, 0x7f,
    0x5b, 0xaf, 0x9e, 0xbb, 0xf1, 0x9a, 0xb1, 0xfd, 0x2c, 0xa0, 0x11, 0x63,
    0x3e, 0x6d, 0xb5, 0xa1, 0xbc, 0x7d, 0xa1, 0x65, 0x7d, 0xaa, 0xeb, 0xcd,
    0xf5, 0x99, 0x9f, 0x58, 0x9f, 0x0b, 0xf4, 0xfa, 0x70, 0x1b, 0x4b, 0x4f,
    0xbb, 0x3e, 0x0b, 0x66, 0xac, 0x0f, 0xda, 0xe8, 0x98, 0x7f, 0x9a, 0xf5,
    0x59, 0x9d, 0x58, 0x9f, 0xe1, 0x29, 0xeb, 0x93, 0x25, 0x57, 0x43, 0x50,
    0x7b, 0xbd, 0x7a, 0xa6, 0xe9, 0xbd, 0x68, 0x3e, 0xea, 0xcc, 0xa1, 0xd4,
    0x8b, 0xc5, 0x15, 0xe2, 0x16, 0xfb, 0xfd, 0xf1, 0x94, 0x4a, 0xa9, 0x59,
    0x94, 0x8a, 0x1e, 0xa4, 0x35, 0x08, 0xee, 0xc1, 0xd5, 0x48, 0xa7, 0x99,
    0xb6, 0x2d, 0x1a, 0xb3, 0x66, 0x5b, 0xe3, 0xd3, 0xc9, 0x98, 0xdf, 0x58,
    0x6f, 0xee, 0x9d, 0x66, 0x99, 0x93, 0x22, 0x6f, 0xb9, 0x44, 0x5b, 0xea,
    0x95, 0x0f, 0xf7, 0xb6, 0x2f, 0xa2, 0x81, 0x8e, 0x62, 0x1a, 0xec, 0x2c,
    0xa1, 0x12, 0xc7, 0x12, 0x0a, 0x0d, 0x1d, 0x24, 0x67, 0x7d, 0x96, 0x23,
    0xc7, 0x91, 0x9c, 0x5f, 0x6f, 0xbd, 0xf2, 0x1b, 0xe6, 0xfe, 0x5c, 0x84,
    0xfd, 0x59, 0x4c, 0xdb, 0xe0, 0x55, 0xe7, 0x26, 0xfc, 0x3c, 0xd7, 0x27,
    0xcf, 0x22, 0xb7, 0x1b, 0x2b, 0x57, 0x4a, 0x25, 0x36, 0xd4, 0x55, 0x7b,
    0x09, 0x39, 0x6d, 0x59, 0xb6, 0x1c, 0x5b, 0x72, 0xaf, 0x8f, 0xd4, 0x4f,
    0xdd, 0xeb, 0x6e, 0xd4, 0x55, 0x2a, 0xd7, 0x48, 0xb5, 0xe5, 0xd3, 0x6b,
    0x39, 0x22, 0xca, 0x4d, 0x1d, 0xc3, 0x43, 0x23, 0xb6, 0x32, 0xac, 0xe5,
    0x62, 0x8b, 0x9f, 0x9a, 0xd0, 0xf5, 0x8c, 0x88, 0xa5, 0xd0, 0xcb, 0x90,
    0x7a, 0x4b, 0x64, 0x7f, 0xec, 0xd2, 0xb6, 0xf6, 0xd5, 0xab, 0x7b, 0xaa,
    0xcb, 0xd3, 0x50, 0x96, 0x2d, 0xfd, 0x69, 0x3a, 0xfb, 0x1f, 0xe8, 0x84,
    0x9c, 0x69, 0xda, 0xe7, 0x5e, 0x09, 0x0b, 0xb4, 0xda, 0xe5, 0x65, 0xf5,
    0x2a, 0x06, 0xf1, 0xc2, 0xda, 0xbc, 0x88, 0x86, 0x72, 0xcc, 0xb5, 0x45,
    0x9b, 0x57, 0xd5, 0xab, 0x78, 0x7a, 0x21, 0xa2, 0x91, 0x45, 0xb0, 0x9e,
    0x91, 0x8e, 0x4a, 0xac, 0xec, 0xd7, 0xd0, 0x5a, 0x86, 0xe1, 0xed, 0x82,
    0x7e, 0x43, 0x19, 0x35, 0xdb, 0xf2, 0xc8, 0xe4, 0x43, 0x4e, 0xf6, 0x27,
    0xc9, 0x54, 0x8a, 0x4e, 0x8d, 0xd4, 0x95, 0x69, 0xa9, 0x07, 0xfa, 0x73,
    0x12, 0x7c, 0xc8, 0x39, 0x3f, 0xa1, 0xe1, 0x41, 0xc4, 0x98, 0x6d, 0xcc,
    0x11, 0xc5, 0xb4, 0x94, 0xc3, 0x3a, 0xec, 0x09, 0x21, 0x63, 0x20, 0xae,
    0xb1, 0x46, 0xfa, 0x2a, 0xb6, 0xe7, 0xa6, 0xcb, 0x1a, 0x6b, 0x9a, 0x64,
    0xdf, 0xb9, 0xf7, 0xb7, 0x9a, 0xeb, 0x29, 0xfb, 0xce, 0xf5, 0x15, 0x49,
    0x8b, 0x0b, 0x39, 0x39, 0x1a, 0xc9, 0x90, 0xfe, 0x51, 0x8d, 0xc5, 0xa0,
    0x3b, 0xeb, 0x55, 0x4c, 0xa3, 0xc6, 0xe2, 0xed, 0x40, 0x89, 0x1c, 0xf4,
    0x46, 0x24, 0x79, 0x2e, 0x2d, 0xe7, 0xc8, 0xb9, 0x84, 0xf7, 0x9d, 0xe1,
    0x6a, 0x68, 0xa8, 0xc7, 0xf9, 0xe6, 0xbc, 0x58, 0x4a, 0x9b, 0x45, 0x9a,
    0x3c, 0x23, 0x99, 0x2f, 0xc6, 0x8d, 0xb9, 0x98, 0xb8, 0xad, 0x1c, 0x52,
    0x3d, 0x56, 0x3e, 0x99, 0xfb, 0x5c, 0x99, 0xb0, 0xc3, 0x0a, 0xb9, 0x2e,
    0xdc, 0xcf, 0xa3, 0xda, 0x96, 0x46, 0x04, 0x3c, 0xab, 0xf3, 0x25, 0xb9,
    0x6e, 0x95, 0x58, 0xdf, 0x0a, 0xb9, 0x76, 0x6a, 0x9f, 0x1e, 0xaf, 0x57,
    0xb1, 0xb2, 0xee, 0x5f, 0x5d, 0x25, 0xfa, 0x96, 0x0e, 0x2b, 0xac, 0xc0,
    0x2e, 0xe4, 0x1d, 0xf8, 0x51, 0xac, 0x64, 0x31, 0xf4, 0xad, 0xed, 0x65,
    0xcb, 0x76, 0x9c, 0x09, 0xbb, 0x7f, 0x5a, 0xaf, 0xe5, 0xc5, 0xce, 0x49,
    0x39, 0x0f, 0x39, 0x7a, 0x9d, 0xd9, 0x36, 0x9e, 0xd7, 0x73, 0x15, 0x72,
    0x2e, 0x96, 0x63, 0x28, 0xa2, 0xc7, 0x51, 0x6b, 0x8b, 0xe4, 0x4b, 0x84,
    0x4d, 0x3e, 0x03, 0x50, 0x67, 0xbe, 0xa0, 0x97, 0xea, 0xd5, 0x33, 0x65,
    0x1e, 0xaf, 0x43, 0xce, 0x27, 0x7f, 0x4b, 0x91, 0x21, 0x5c, 0x46, 0x33,
    0x34, 0xb3, 0x8d, 0x85, 0x02, 0xb7, 0x50, 0xc1, 0x51, 0x97, 0x5d, 0x46,
    0x35, 0x76, 0xdc, 0x24, 0x11, 0xb7, 0xb5, 0xaf, 0x30, 0x4b, 0xf0, 0x09,
    0x5e, 0xfb, 0x25, 0x72, 0xdb, 0xbc, 0xed, 0xcb, 0x21, 0x2b, 0x90, 0xbd,
    0xf1, 0x18, 0xb0, 0xda, 0xda, 0x71, 0xea, 0xb4, 0xbb, 0x72, 0x43, 0xed,
    0x36, 0x72, 0x94, 0x71, 0xf4, 0xc2, 0x7e, 0x28, 0x51, 0xce, 0xc1, 0xa5,
    0x06, 0xa8, 0xc2, 0xce, 0x76, 0xea, 0xe5, 0x5a, 0x6d, 0x73, 0x1c, 0xc5,
    0x62, 0xa9, 0x43, 0xc5, 0x72, 0x4d, 0x72, 0x9c, 0x9d, 0x18, 0x87, 0x43,
    0xfa, 0x9a, 0xff, 0xa8, 0x57, 0xf7, 0x0c, 0xef, 0x09, 0xac, 0x9e, 0x7b,
    0x21, 0xdd, 0x88, 0xf8, 0xa5, 0xc5, 0x66, 0x27, 0x57, 0x7a, 0x4e, 0xba,
    0x47, 0xc0, 0xf2, 0xbf, 0xa1, 0x56, 0x35, 0x9d, 0xb2, 0xd2, 0xd9, 0x26,
    0xd2, 0x29, 0xc3, 0x3e, 0xf0, 0xb5, 0xc5, 0x94, 0x7a, 0x85, 0xfd, 0xe8,
    0xbe, 0xd4, 0x3e, 0xd4, 0x95, 0x23, 0x38, 0xc6, 0xe2, 0x39, 0xfb, 0x59,
    0x3d, 0xcf, 0x3d, 0xfb, 0x06, 0xae, 0x6b, 0x01, 0x6e, 0x5b, 0xba, 0x2e,
    0xec, 0x64, 0x0f, 0x56, 0xd6, 0xdb, 0x61, 0xb5, 0x90, 0x52, 0xe9, 0x99,
    0x07, 0xba, 0x17, 0x53, 0xc4, 0x3b, 0x44, 0xee, 0xdc, 0x1c, 0x4a, 0xd1,
    0xbb, 0xf4, 0xd7, 0xf5, 0xea, 0xfb, 0xb0, 0x85, 0xf2, 0xa6, 0x1e, 0x71,
    0xef, 0x47, 0xc4, 0x90, 0x65, 0x87, 0xe7, 0xb5, 0xa3, 0x3f, 0x9d, 0x1e,
    0x6a, 0x41, 0x24, 0x5b, 0xb1, 0x20, 0xea, 0x7e, 0x19, 0x33, 0x9c, 0x65,
    0x2f, 0xb1, 0x97, 0x51, 0x8b, 0x1d, 0xf3, 0x87, 0x9e, 0x78, 0x9b, 0xea,
    0xd0, 0xc2, 0x0e, 0xf6, 0xcd, 0xc2, 0x63, 0xac, 0x45, 0xd9, 0xa3, 0x18,
    0x2b, 0xcf, 0xc8, 0x3c, 0x78, 0x9e, 0x1c, 0x3b, 0xeb, 0xcc, 0xc1, 0xe7,
    0x52, 0x3b, 0xaf, 0xfd, 0x32, 0x39, 0x17, 0xcb, 0xe5, 0xf3, 0x36, 0xee,
    0xff, 0xff, 0x6a, 0x1b, 0xf3, 0xe0, 0xe6, 0x12, 0x19, 0x1a, 0x25, 0x67,
    0x57, 0xa4, 0xf6, 0xc3, 0x32, 0x9a, 0xca, 0x31, 0xec, 0xb2, 0x6f, 0xb8,
    0x17, 0x27, 0x74, 0xd0, 0xef, 0x5a, 0x3f, 0xfc, 0x54, 0x52, 0x27, 0x55,
    0xc7, 0x2e, 0x29, 0x0d, 0xea, 0xfb, 0x3b, 0x6f, 0x7f, 0x2d, 0x7a, 0xf3,
    0x98, 0x5c, 0x39, 0x8e, 0x5f, 0x79, 0xfd, 0x9b, 0xc5, 0x12, 0x19, 0xbf,
    0x92, 0xb4, 0x99, 0x25, 0xe4, 0x75, 0xa9, 0x39, 0x31, 0x64, 0x4c, 0xbd,
    0x04, 0x34, 0xc3, 0xe6, 0x6a, 0x6c, 0xe8, 0x7a, 0xfb, 0x03, 0xb6, 0x40,
    0x96, 0x36, 0x1b, 0xbf, 0xfc, 0x80, 0xd7, 0x56, 0x6a, 0xb8, 0xef, 0xa5,
    0x9d, 0xd8, 0x57, 0xcd, 0xc6, 0x8f, 0x3f, 0xc8, 0x11, 0xae, 0x94, 0x9c,
    0x94, 0x14, 0xbd, 0x93, 0x5d, 0x0d, 0xea, 0xbe, 0xa2, 0xe6, 0xcc, 0xdb,
    0xa1, 0x5a, 0xe6, 0x99, 0xe6, 0x96, 0x85, 0x2c, 0x93, 0x2f, 0x5b, 0x86,
    0xcc, 0x28, 0x81, 0x3f, 0xf3, 0x96, 0xa9, 0x96, 0x65, 0xc4, 0x8a, 0xd5,
    0x40, 0x74, 0x6d, 0x6b, 0x18, 0xfc, 0xed, 0x07, 0x98, 0x23, 0xcc, 0x95,
    0x0b, 0x9e, 0x1c, 0x76, 0x43, 0x6c, 0x37, 0x3c, 0xee, 0xc5, 0xb2, 0x95,
    0x25, 0x32, 0x9e, 0xe3, 0x31, 0x16, 0x37, 0x28, 0x5f, 0xe0, 0xad, 0xe5,
    0xc8, 0xf2, 0x29, 0x3d, 0xc6, 0x17, 0xa7, 0x8d, 0x75, 0x81, 0x65, 0xac,
    0xcd, 0xd8, 0x89, 0xd0, 0x75, 0x3f, 0x01, 0xef, 0xc9, 0xe9, 0x0a, 0x94,
    0x0b, 0xca, 0xde, 0xf0, 0xa8, 0x3a, 0x65, 0x0f, 0x4f, 0xf1, 0xa8, 0xec,
    0x39, 0x76, 0xd2, 0xed, 0xc0, 0x03, 0x34, 0x98, 0x3e, 0x87, 0xc7, 0xc5,
    0xbb, 0x08, 0xfb, 0x45, 0xb4, 0x60, 0x07, 0x47, 0xdc, 0x31, 0xb6, 0x0b,
    0xb4, 0x82, 0x78, 0x3e, 0x0f, 0x9e, 0xcf, 0xee, 0xad, 0xc3, 0x2e, 0x70,
    0x3f, 0x4e, 0x1d, 0xbc, 0xcf, 0x3c, 0xcd, 0xc2, 0x2e, 0xfd, 0x61, 0xc4,
    0x1d, 0x97, 0xf6, 0xd3, 0x6c, 0x57, 0x69, 0x8c, 0xcd, 0x9e, 0x6d, 0x57,
    0x56, 0xa0, 0xc6, 0xb6, 0x54, 0xfb, 0x9a, 0x14, 0x79, 0x26, 0x19, 0xb4,
    0xb2, 0x41, 0xdf, 0xb7, 0xda, 0x1b, 0xa4, 0xcf, 0xf6, 0x76, 0x36, 0xc0,
    0x1f, 0xdc, 0xa5, 0xa3, 0xe6, 0xbb, 0xb8, 0xcf, 0xd8, 0x9b, 0x4f, 0xd2,
    0x80, 0x2d, 0xcb, 0x51, 0xe2, 0x68, 0x46, 0xaf, 0x3e, 0x8d, 0xbc, 0x0c,
    0xcc, 0x56, 0x36, 0xe6, 0xac, 0xc2, 0x06, 0xca, 0x1e, 0xc6, 0xc1, 0x3e,
    0x98, 0x63, 0xeb, 0x42, 0xe9, 0x83, 0x31, 0xa2, 0xcb, 0x5a, 0x16, 0x16,
    0xc1, 0xe7, 0xa5, 0xe8, 0xb1, 0x75, 0x34, 0xa8, 0xef, 0x36, 0x23, 0xce,
    0xeb, 0x31, 0xaa, 0x8b, 0xe5, 0xb3, 0xeb, 0x2c, 0x1a, 0x69, 0xaf, 0x86,
    0x75, 0x75, 0x42, 0xca, 0xcf, 0x9a, 0x47, 0xe0, 0x11, 0xac, 0xa9, 0xfa,
    0x29, 0xa9, 0x06, 0x79, 0xd7, 0x8a, 0x38, 0x8f, 0xc9, 0x54, 0xed, 0x94,
    0x54, 0xd5, 0x14, 0xcd, 0x3a, 0x1a, 0xe9, 0x59, 0x8e, 0xb6, 0x33, 0xe0,
    0x73, 0x0a, 0xe5, 0x48, 0x4a, 0x1c, 0xf3, 0x20, 0xab, 0x81, 0x0c, 0x37,
    0x11, 0x07, 0xdf, 0x38, 0x0e, 0xc9, 0xd8, 0xa5, 0x9f, 0xd8, 0x63, 0xdf,
    0x21, 0x75, 0xf8, 0xf6, 0xa5, 0xe8, 0x73, 0x3c, 0x42, 0xcc, 0xb5, 0x47,
    0xae, 0xa8, 0x43, 0xae, 0x60, 0x09, 0x66, 0xfb, 0x52, 0x8c, 0xb0, 0xe2,
    0x5d, 0x8f, 0xd1, 0x86, 0xd8, 0xe7, 0x72, 0xac, 0xc9, 0x7d, 0xf2, 0xa6,
    0x71, 0x44, 0x9f, 0x94, 0xf7, 0x63, 0xad, 0x43, 0xce, 0x07, 0xd8, 0x76,
    0x65, 0x54, 0xe2, 0xa4, 0x8a, 0x9f, 0x46, 0x9c, 0x57, 0x61, 0xb4, 0x7c,
    0x7e, 0x6e, 0x4f, 0xc4, 0x27, 0xab, 0x7c, 0x2a, 0xd6, 0x32, 0xe8, 0x22,
    0xcc, 0xc9, 0x83, 0x72, 0xee, 0xab, 0x66, 0xd9, 0x3b, 0x73, 0x2d, 0xf6,
    0x54, 0x0c, 0x5f, 0x50, 0x83, 0x1e, 0x34, 0xf0, 0xed, 0xef, 0xbf, 0x94,
    0xfe, 0x23, 0x96, 0x1d, 0xc0, 0x1e, 0xf1, 0x8b, 0xa8, 0xd5, 0xdb, 0xa1,
    0x2c, 0x54, 0xed, 0x85, 0x17, 0xa7, 0xed, 0x89, 0x3c, 0xcb, 0x9e, 0x80,
    0x85, 0x76, 0x29, 0x0b, 0x2d, 0x93, 0x69, 0xb6, 0xd0, 0xbd, 0x1c, 0xdb,
    0xbd, 0xcd, 0xe7, 0x76, 0x3d, 0xac, 0xc7, 0xdb, 0x5e, 0x77, 0x3a, 0x6b,
    0x47, 0x5d, 0x45, 0x66, 0xef, 0x50, 0x76, 0x39, 0xea, 0xaa, 0xb3, 0xd4,
    0xb5, 0x16, 0xe5, 0x1e, 0x94, 0x7b, 0xb8, 0xc4, 0xe0, 0x73, 0xfd, 0x43,
    0x5c, 0xef, 0x2f, 0x47, 0x3a, 0xea, 0x50, 0xe7, 0x6c, 0x5e, 0x62, 0xe1,
    0x34, 0x2f, 0x81, 0x28, 0xc1, 0xc8, 0x92, 0x94, 0x23, 0x04, 0x43, 0x73,
    0x6c, 0x49, 0xea, 0xdc, 0x9d, 0xcf, 0x16, 0xfc, 0xa6, 0xaa, 0x8b, 0x67,
    0x81, 0xa3, 0x47, 0xbb, 0xe5, 0xac, 0x63, 0x7b, 0x5e, 0x0e, 0x9d, 0x07,
    0x12, 0x67, 0xeb, 0xfd, 0x42, 0x9d, 0x55, 0x88, 0x51, 0x1a, 0xd4, 0xb3,
    0x18, 0x65, 0xef, 0x5e, 0x98, 0xa5, 0x77, 0x20, 0x55, 0x46, 0x00, 0xf0,
    0x46, 0x06, 0x5b, 0x3c, 0xf7, 0x7b, 0x60, 0x30, 0x4d, 0xee, 0x02, 0xe9,
    0x2f, 0xe0, 0x51, 0x4b, 0x6d, 0x3c, 0x1f, 0x35, 0xe4, 0x1d, 0x4a, 0xc5,
    0x1e, 0x09, 0xa1, 0xe5, 0xfe, 0x3c, 0xb4, 0x67, 0xf1, 0x19, 0xf9, 0x68,
    0x65, 0xbe, 0x8c, 0x3b, 0x6c, 0xea, 0x59, 0x93, 0x5c, 0x63, 0x8f, 0x58,
    0x59, 0xca, 0x77, 0x3b, 0x43, 0xc6, 0x15, 0x37, 0xea, 0x3d, 0xc0, 0x9e,
    0x28, 0xcd, 0x12, 0x77, 0x44, 0xdc, 0x07, 0xb0, 0x67, 0x32, 0x70, 0x17,
    0xb6, 0x61, 0x14, 0xfa, 0xb4, 0x54, 0x3a, 0x86, 0xee, 0x99, 0xcd, 0x1b,
    0xaf, 0x37, 0x7b, 0xe4, 0xe0, 0x7d, 0xef, 0xc5, 0xd9, 0x24, 0x77, 0xb9,
    0xe0, 0xbb, 0xb4, 0xf4, 0xa5, 0x76, 0x57, 0x93, 0x2b, 0xa5, 0xc1, 0x5b,
    0x4e, 0xa6, 0x37, 0x75, 0xe5, 0x35, 0xc3, 0x03, 0x98, 0xfe, 0xd4, 0x3b,
    0xa9, 0x3c, 0xc6, 0x62, 0xec, 0xde, 0x66, 0x07, 0xee, 0xf4, 0x32, 0xfd,
    0x04, 0xd2, 0x59, 0x8e, 0xd4, 0xe1, 0x92, 0x14, 0xbb, 0x96, 0x3c, 0x0d,
    0x49, 0x85, 0xe1, 0xb2, 0x7b, 0xe0, 0x37, 0x23, 0xfd, 0xbb, 0x29, 0x3a,
    0xee, 0x49, 0xab, 0xa0, 0x48, 0xfb, 0x1e, 0x3a, 0xb0, 0xd4, 0x1b, 0x49,
    0xf4, 0xc2, 0x90, 0x27, 0x76, 0x9a, 0x87, 0xd6, 0x63, 0xdf, 0xf1, 0x37,
    0x4a, 0xcd, 0x76, 0x07, 0xe5, 0x1a, 0xaf, 0xa5, 0x56, 0xd8, 0x72, 0x8d,
    0x31, 0xba, 0xdc, 0x13, 0x6a, 0x3f, 0x49, 0x87, 0xea, 0xb9, 0x7f, 0x73,
    0xd2, 0x70, 0x26, 0xa7, 0xa9, 0xef, 0x40, 0xc6, 0xe5, 0xcc, 0x1c, 0xc0,
    0x68, 0x0d, 0x79, 0xca, 0x1c, 0xc5, 0x9c, 0xb4, 0x8b, 0x84, 0x25, 0xdf,
    0x34, 0xf3, 0xd4, 0xf4, 0x5e, 0xa1, 0xda, 0x94, 0x9e, 0x08, 0xfd, 0xaf,
    0x97, 0xf6, 0xf7, 0x01, 0x79, 0xaf, 0xc0, 0xfa, 0xbb, 0x1f, 0xa5, 0x9b,
    0x91, 0x6e, 0x41, 0x8c, 0x61, 0xd1, 0x13, 0x3c, 0x0a, 0x8c, 0xf0, 0x72,
    0xd6, 0x78, 0x98, 0x6e, 0xc2, 0x3a, 0xb8, 0xd1, 0xcb, 0x8d, 0xbc, 0x43,
    0x6e, 0xb6, 0x9e, 0x41, 0x8b, 0x25, 0x2d, 0xb2, 0xcd, 0x41, 0x89, 0xd5,
    0xf2, 0x09, 0xc2, 0x88, 0x6b, 0xce, 0x69, 0x74, 0xd2, 0x2d, 0x3a, 0x59,
    0xa7, 0xd1, 0xa9, 0xb0, 0xe8, 0xcc, 0x3d, 0x8d, 0x4e, 0x93, 0x45, 0x27,
    0x7b, 0x9a, 0x8e, 0xb2, 0xc2, 0xad, 0xae, 0x4c, 0x1a, 0xb8, 0x79, 0x31,
    0x6d, 0x75, 0xe5, 0x4c, 0xcb, 0x5f, 0xa2, 0xf3, 0x5d, 0x88, 0x8e, 0x94,
    0xf7, 0xc3, 0x3d, 0xcb, 0x95, 0x31, 0x25, 0x85, 0xdb, 0xa4, 0x21, 0xca,
    0xbc, 0xb7, 0xaa, 0x72, 0x88, 0x0e, 0xec, 0x21, 0xa7, 0x87, 0x4f, 0x12,
    0x7b, 0x53, 0x17, 0xfa, 0x9d, 0x97, 0x01, 0xef, 0x47, 0x72, 0x5e, 0xbc,
    0xf4, 0x23, 0x9b, 0x4d, 0x54, 0xfc, 0x6e, 0xba, 0x76, 0x99, 0x45, 0xdb,
    0x39, 0x43, 0xdb, 0x7b, 0x8b, 0xf5, 0x2c, 0x2d, 0x97, 0xb4, 0xb4, 0xb1,
    0xa9, 0xb3, 0x98, 0xd6, 0x39, 0x6c, 0xe8, 0x63, 0x09, 0x7b, 0xcf, 0x14,
    0x57, 0x41, 0xc3, 0x3e, 0x07, 0x79, 0xf3, 0x33, 0xa8, 0xc2, 0xe6, 0xcd,
    0x77, 0xa2, 0x44, 0x31, 0xcb, 0x53, 0x43, 0xce, 0x45, 0x3a, 0x72, 0xfb,
    0x3c, 0xed, 0x73, 0xfc, 0xc8, 0x30, 0x44, 0xc5, 0x2f, 0x2f, 0xc2, 0x21,
    0x9b, 0x6b, 0x2b, 0xa5, 0x95, 0x65, 0x98, 0xfb, 0x3b, 0xad, 0x31, 0x98,
    0x9b, 0x63, 0xb0, 0x74, 0x58, 0xe3, 0x5f, 0xb6, 0xce, 0x4f, 0xc7, 0xea,
    0xde, 0xc2, 0x3b, 0x7f, 0x89, 0xd5, 0x4a, 0xd8, 0xd6, 0x55, 0xbc, 0x9d,
    0x9f, 0x88, 0xb7, 0xf9, 0xb4, 0x2c, 0x94, 0x32, 0x8c, 0x20, 0x07, 0xde,
    0x44, 0xc6, 0xe2, 0xa9, 0xc8, 0x63, 0x2f, 0x22, 0x74, 0x1b, 0xf3, 0x51,
    0xb7, 0x8c, 0x90, 0xd3, 0xb1, 0x8b, 0xd3, 0x79, 0x0f, 0x37, 0x08, 0x21,
    0x9f, 0xb5, 0x3b, 0x30, 0x7e, 0xfe, 0x8e, 0x47, 0xc5, 0xda, 0xcf, 0x19,
    0x97, 0x35, 0x7e, 0xc5, 0x78, 0xce, 0x50, 0xfb, 0x98, 0xbd, 0xc8, 0xeb,
    0x0d, 0xea, 0x9d, 0x1d, 0x6f, 0x44, 0xfa, 0x90, 0x8b, 0x1b, 0xe4, 0x4e,
    0x4e, 0xb3, 0xf8, 0x0f, 0xb6, 0x3f, 0x8e, 0xbf, 0xcd, 0xbd, 0x2c, 0x73,
    0x65, 0x74, 0x97, 0x2a, 0x9f, 0x79, 0xa9, 0x7d, 0xeb, 0x35, 0x66, 0xae,
    0xbd, 0x37, 0xa6, 0xbc, 0xae, 0x7a, 0x0e, 0xf8, 0xa2, 0xf5, 0x79, 0x20,
    0xf6, 0xbb, 0xc7, 0x7c, 0x1e, 0x88, 0x48, 0x70, 0x39, 0x0d, 0xf4, 0x34,
    0x99, 0x2d, 0xa7, 0x34, 0xf8, 0xff, 0xf8, 0x81, 0x2c, 0x8b, 0x1d, 0x8d,
    0x08, 0xc0, 0x56, 0x62, 0x53, 0x5e, 0xd8, 0x26, 0x79, 0xe5, 0x85, 0x11,
    0x49, 0x7e, 0xcf, 0x9b, 0x86, 0xf9, 0x48, 0xc3, 0xbc, 0xa4, 0x29, 0xef,
    0x9a, 0xa6, 0xb9, 0x14, 0xe6, 0x0e, 0xf0, 0x7e, 0xf9, 0x1c, 0xb8, 0x62,
    0xcc, 0xeb, 0xcd, 0xe8, 0x8f, 0xb9, 0x77, 0xf1, 0x49, 0x4b, 0xd3, 0x6c,
    0xf2, 0x76, 0x2c, 0xe4, 0xbd, 0x7c, 0x1b, 0x6a, 0x8b, 0x49, 0x3f, 0x67,
    0x93, 0xef, 0x30, 0x29, 0x7f, 0x37, 0x29, 0xdf, 0x6d, 0xb2, 0xeb, 0x67,
    0x81, 0x7f, 0x6e, 0x50, 0x77, 0x7c, 0xaf, 0xbb, 0x46, 0x7a, 0xcd, 0x1a,
    0xaa, 0x75, 0xa9, 0xe7, 0x2d, 0x42, 0xe6, 0xff, 0x05, 0xf9, 0xfb, 0x89,
    0xef, 0x31, 0xa5, 0x74, 0x11, 0xe6, 0xdb, 0xe5, 0x5a, 0xe9, 0xdc, 0x4c,
    0x2e, 0x37, 0xcf, 0x22, 0x9f, 0x10, 0x7a, 0x36, 0x45, 0xb9, 0xdb, 0x8b,
    0xd3, 0x94, 0xd3, 0xc7, 0xf5, 0xc9, 0xf1, 0xc2, 0xb4, 0x13, 0xa4, 0x60,
    0x4a, 0xec, 0xc5, 0x31, 0x8a, 0xf4, 0x1c, 0x03, 0x88, 0x0b, 0xe0, 0xb9,
    0x23, 0xb5, 0xe7, 0xd2, 0xa0, 0x60, 0x1b, 0xae, 0xa7, 0x8a, 0x5f, 0x7b,
    0xfb, 0x67, 0x3b, 0x6d, 0x9b, 0xa6, 0x9e, 0xb6, 0x03, 0x35, 0x34, 0x90,
    0x9b, 0x42, 0x5e, 0x6f, 0x35, 0x85, 0x70, 0x0e, 0xd6, 0xe7, 0xc1, 0xee,
    0x51, 0xce, 0x23, 0x54, 0x6d, 0xe7, 0x19, 0x5c, 0x9b, 0x93, 0x66, 0xaf,
    0x2b, 0x7b, 0x46, 0x5d, 0x7c, 0xbf, 0xa9, 0xc7, 0x67, 0xc5, 0x1f, 0xad,
    0xcf, 0x1a, 0x33, 0x1b, 0x49, 0x3e, 0x79, 0xf2, 0x3a, 0xeb, 0xa4, 0xdf,
    0x75, 0xeb, 0x67, 0x88, 0xec, 0x19, 0x73, 0x1a, 0xd5, 0xfb, 0x66, 0x3a,
    0x0a, 0xc4, 0xcd, 0x07, 0xab, 0x37, 0xa0, 0xbd, 0xa4, 0x25, 0xf6, 0xe3,
    0xb1, 0xab, 0xf4, 0x13, 0x48, 0x73, 0x8b, 0xf6, 0x99, 0xf6, 0x5e, 0xa7,
    0xfc, 0x7a, 0x07, 0xe9, 0xf8, 0x10, 0xa5, 0x38, 0x56, 0x09, 0xc9, 0xb8,
    0x80, 0xcf, 0x7d, 0xd6, 0xa9, 0xa7, 0xa9, 0xd6, 0xab, 0x63, 0x4e, 0x58,
    0x5d, 0x3e, 0x79, 0xbb, 0x13, 0xfe, 0x01, 0xf3, 0x78, 0x19, 0xe5, 0xca,
    0xfd, 0x63, 0xa7, 0x8a, 0xb4, 0x22, 0xfb, 0x42, 0x2a, 0x12, 0x85, 0x32,
    0x9a, 0xca, 0x53, 0xfb, 0x49, 0x46, 0x9b, 0x7c, 0x6f, 0xe8, 0x44, 0xbd,
    0x7d, 0xa4, 0x9e, 0x6b, 0xf0, 0xbb, 0x76, 0x99, 0xd2, 0x46, 0xb6, 0x5e,
    0xd6, 0xd8, 0xbf, 0x35, 0xf1, 0x8c, 0x62, 0xa9, 0x9e, 0x03, 0xf5, 0xec,
    0x65, 0x9b, 0x68, 0x44, 0x0d, 0xfc, 0x04, 0x86, 0x9f, 0x5b, 0x54, 0x37,
    0xaa, 0x3b, 0xee, 0xe0, 0xe5, 0x8d, 0x94, 0x6f, 0xe3, 0x77, 0x05, 0x85,
    0x8d, 0x6f, 0x80, 0x6b, 0x32, 0x33, 0x28, 0xf5, 0x50, 0xea, 0x2d, 0xe2,
    0x7e, 0xf1, 0xa4, 0xfd, 0xeb, 0xfb, 0xd2, 0x0e, 0x93, 0xf9, 0xfc, 0x5b,
    0x3d, 0xa5, 0x68, 0xd6, 0xef, 0x8a, 0x85, 0x9c, 0x9f, 0x91, 0x4f, 0xae,
    0x98, 0x62, 0x1f, 0xe0, 0x3e, 0x5a, 0xef, 0xe4, 0xfb, 0xa8, 0xdc, 0x3b,
    0xed, 0x4d, 0x34, 0x50, 0xd7, 0x44, 0x0d, 0x4e, 0x27, 0xf8, 0x95, 0xe0,
    0x57, 0x82, 0xcf, 0xa0, 0x81, 0xee, 0x55, 0x34, 0x50, 0xbb, 0x8a, 0x1a,
    0x70, 0xb6, 0xba, 0xf2, 0xb2, 0xed, 0xfc, 0x1d, 0x81, 0x5d, 0x5a, 0x39,
    0xd1, 0x39, 0x8d, 0xea, 0x3d, 0xb3, 0x81, 0xda, 0x26, 0x32, 0x6d, 0x94,
    0xcb, 0x99, 0xa7, 0xce, 0x40, 0xfd, 0xaa, 0xc4, 0x1c, 0x96, 0xd7, 0x0e,
    0x91, 0xd3, 0x60, 0x9b, 0x3d, 0x91, 0x78, 0xb6, 0x60, 0xde, 0xbb, 0xbb,
    0x1a, 0xa7, 0x3e, 0xab, 0x35, 0xe5, 0xe7, 0x36, 0x4e, 0x7f, 0x86, 0x3b,
    0xf3, 0x19, 0xff, 0x6c, 0x32, 0xf3, 0x79, 0x63, 0x5f, 0xa3, 0x7a, 0x0f,
    0xe7, 0x22, 0xde, 0x53, 0x38, 0xf1, 0x9a, 0x60, 0x03, 0x17, 0x4b, 0x2d,
    0xbe, 0xb1, 0x15, 0xd1, 0xe7, 0x89, 0xe3, 0x61, 0x15, 0x97, 0xa5, 0x60,
    0x56, 0x32, 0xf5, 0xb7, 0x1a, 0x36, 0x1d, 0x8f, 0x7a, 0xf5, 0x3b, 0x73,
    0xfc, 0xdc, 0x9e, 0xdf, 0xf7, 0x70, 0x09, 0x8e, 0x48, 0x72, 0x44, 0xf2,
    0x59, 0x3d, 0xa7, 0x16, 0xca, 0x77, 0x43, 0x76, 0x8a, 0xd5, 0xd4, 0x22,
    0xd2, 0xb1, 0x23, 0xf8, 0xd6, 0xba, 0x06, 0xf6, 0x12, 0x80, 0xa4, 0x98,
    0x76, 0x1a, 0xab, 0x71, 0xa7, 0x9a, 0x83, 0xb8, 0x63, 0xa9, 0xe1, 0x92,
    0xa5, 0x84, 0xfe, 0xee, 0x8c, 0xff, 0x2b, 0x25, 0xb3, 0x2d, 0x5f, 0xa3,
    0xfa, 0x4e, 0xcf, 0x8c, 0xd2, 0x0d, 0xed, 0x17, 0x5d, 0xe8, 0x8f, 0xd9,
    0x42, 0xab, 0x6e, 0xa1, 0x12, 0x2d, 0x44, 0x64, 0x0b, 0xad, 0xb2, 0x85,
    0xd6, 0x44, 0x0b, 0xea, 0x19, 0x05, 0xb7, 0xb1, 0x28, 0xd1, 0xc6, 0x42,
    0x4a, 0xd5, 0xeb, 0x35, 0xd1, 0xa8, 0xbe, 0x0f, 0x4b, 0xde, 0x14, 0x5c,
    0xa5, 0xe6, 0x5a, 0xb9, 0x5c, 0x89, 0x95, 0x82, 0x77, 0x29, 0x95, 0x27,
    0xc9, 0x71, 0x1d, 0x2f, 0xbf, 0x30, 0x2d, 0x6e, 0xae, 0x9c, 0x12, 0x37,
    0xb3, 0x77, 0x91, 0xb1, 0xa2, 0xfb, 0x76, 0xdc, 0x11, 0x67, 0xf3, 0xd2,
    0xa7, 0x3e, 0x48, 0x7a, 0xe9, 0x1c, 0xf2, 0x38, 0x4a, 0xe4, 0x73, 0xff,
    0x1e, 0xe9, 0x33, 0xfa, 0xa9, 0xe2, 0xf7, 0x39, 0xda, 0x47, 0xf2, 0x9a,
    0x5d, 0xa2, 0xdf, 0xa9, 0x32, 0x7d, 0xe0, 0x4a, 0xac, 0x59, 0xc8, 0x5d,
    0x2d, 0xe3, 0x83, 0x1c, 0xc3, 0xe5, 0x6c, 0xb0, 0x39, 0xe1, 0xc3, 0xba,
    0xd0, 0x6a, 0x95, 0xec, 0x2d, 0xf7, 0x92, 0x69, 0xc4, 0xfd, 0x0c, 0x3f,
    0x7d, 0xb3, 0x79, 0x0c, 0xf5, 0xcd, 0x92, 0x41, 0x39, 0x49, 0xc3, 0xa0,
    0x99, 0xcf, 0x38, 0xaf, 0x3a, 0x8d, 0xcd, 0x1d, 0xd6, 0xf2, 0x8b, 0x9d,
    0x9f, 0x3a, 0x8d, 0xfc, 0x4e, 0x29, 0xbf, 0xc2, 0x10, 0x34, 0xf7, 0xeb,
    0x77, 0xd2, 0x0f, 0x05, 0x7f, 0x77, 0xf5, 0x3d, 0xfe, 0x7c, 0x80, 0x5e,
    0x65, 0xfe, 0xeb, 0xcc, 0x1b, 0xef, 0x51, 0xab, 0x71, 0xfd, 0x65, 0xfb,
    0xe9, 0x71, 0xd6, 0x13, 0x39, 0xd9, 0x74, 0xb7, 0x81, 0xfe, 0x8c, 0xef,
    0x7d, 0xf9, 0x40, 0xf0, 0x36, 0x7a, 0x83, 0x55, 0xf6, 0xf4, 0x05, 0x83,
    0xd7, 0x6d, 0xbb, 0x3d, 0xb0, 0x6d, 0xdb, 0x9a, 0x93, 0xbd, 0xfd, 0xf4,
    0x49, 0x56, 0xa4, 0x07, 0xed, 0xd0, 0x9a, 0xdb, 0x56, 0x45, 0x9f, 0xb7,
    0x23, 0xd9, 0x46, 0x4f, 0x29, 0xf2, 0xb0, 0xd0, 0xe2, 0x4f, 0x72, 0x5c,
    0xff, 0x57, 0x3e, 0x88, 0x3e, 0x8d, 0xd8, 0x78, 0xdb, 0xc6, 0xdb, 0xe8,
    0x75, 0x43, 0x7d, 0x05, 0x3a, 0xb7, 0xed, 0x40, 0x55, 0x55, 0x55, 0x1b,
    0x5d, 0x09, 0xf9, 0xc8, 0xe6, 0xe0, 0x9d, 0x81, 0x35, 0x55, 0xf4, 0x0b,
    0x83, 0x0b, 0xbf, 0x8d, 0xcf, 0x93, 0x7b, 0xe8, 0x4f, 0x8a, 0x5c, 0x6e,
    0x57, 0xe5, 0x9e, 0xb2, 0xd9, 0x55, 0x31, 0x94, 0xa2, 0xb7, 0x2d, 0x85,
    0x4e, 0xd8, 0xb8, 0xd0, 0x0f, 0x75, 0xf6, 0x27, 0x36, 0xd3, 0x41, 0x9e,
    0x38, 0xf9, 0x0d, 0x9d, 0x3a, 0xf0, 0x78, 0x9d, 0xe6, 0xd2, 0x2d, 0xe9,
    0x4a, 0x61, 0x1b, 0x8a, 0xbf, 0x2c, 0x7b, 0x8d, 0xc6, 0x5f, 0x93, 0xcf,
    0x56, 0x36, 0xb4, 0xd1, 0x3d, 0x0e, 0xa8, 0xfc, 0x0e, 0x4d, 0xf5, 0x55,
    0xf5, 0xed, 0x39, 0x70, 0x70, 0x53, 0xf0, 0x23, 0x9b, 0xda, 0xe8, 0x3e,
    0x16, 0x5e, 0x8b, 0x8f, 0x35, 0x7b, 0xae, 0x3e, 0xb8, 0x67, 0x6f, 0xc9,
    0x7a, 0xfa, 0xa5, 0xd4, 0x47, 0x1d, 0xf4, 0x10, 0x8b, 0xaf, 0x6e, 0xa3,
    0x17, 0x78, 0xc0, 0xf4, 0x86, 0xfc, 0xfc, 0x76, 0x0a, 0x3e, 0x37, 0x5e,
    0xb3, 0x26, 0x50, 0xfd, 0x00, 0xec, 0xf3, 0xd8, 0x1a, 0x3a, 0x92, 0x22,
    0xd5, 0x1f, 0xa2, 0xc7, 0x52, 0x0c, 0x49, 0xaf, 0x95, 0x74, 0xcd, 0xc1,
    0x0b, 0xaf, 0xa9, 0x7e, 0xd3, 0x38, 0xb0, 0xa2, 0x9b, 0xee, 0x51, 0x1a,
    0xf4, 0x88, 0xcc, 0xa0, 0x4f, 0xa7, 0xf2, 0xec, 0xbb, 0x9c, 0x81, 0x83,
    0x01, 0xfa, 0x78, 0x3a, 0xf8, 0x35, 0xc2, 0x95, 0x7d, 0xb2, 0x8a, 0x1e,
    0x91, 0xf2, 0x7c, 0xe7, 0xd5, 0xf4, 0x65, 0x6e, 0x63, 0xcd, 0x9e, 0x03,
    0xd5, 0xb7, 0x55, 0x07, 0xaa, 0xe9, 0x03, 0x96, 0x6f, 0x16, 0x85, 0xae,
    0x6a, 0x7a, 0xca, 0xc1, 0xcb, 0xf8, 0x96, 0xad, 0x75, 0xcf, 0x01, 0xfe,
    0x33, 0x6e, 0xbe, 0x74, 0x7f, 0xd5, 0xd1, 0xbe, 0xa0, 0xc8, 0x73, 0x6d,
    0x16, 0xf9, 0x2e, 0xfa, 0x95, 0x83, 0x2b, 0xce, 0xde, 0x33, 0xb2, 0x66,
    0x44, 0xe4, 0x67, 0xff, 0x7b, 0x55, 0x09, 0x3e, 0xd1, 0x50, 0x35, 0xd0,
    0xb7, 0xed, 0xc2, 0xbd, 0x23, 0x46, 0x70, 0x2b, 0x3d, 0x97, 0x2a, 0x5b,
    0x9c, 0x97, 0x5d, 0x7a, 0x84, 0x3e, 0x99, 0x06, 0xfe, 0xbd, 0x35, 0x1b,
    0x37, 0x9d, 0xac, 0xbb, 0xe6, 0xc3, 0x7d, 0x87, 0x6f, 0xbb, 0x74, 0xdb,
    0xb6, 0xdb, 0x3e, 0xa0, 0x67, 0x58, 0xba, 0xe6, 0xe8, 0x4b, 0x87, 0x6f,
    0x1b, 0xd9, 0x86, 0xda, 0x12, 0xff, 0x9f, 0xdc, 0xf8, 0xd2, 0x81, 0xdb,
    0x4f, 0x3e, 0x79, 0xf2, 0xe8, 0xbb, 0x27, 0x7f, 0x19, 0x38, 0x59, 0xdd,
    0x1f, 0x08, 0x3e, 0xb8, 0xb9, 0xad, 0x73, 0x07, 0x7d, 0x57, 0xd6, 0xf8,
    0xfd, 0xd2, 0x35, 0x0f, 0x56, 0x3f, 0x8c, 0xee, 0x1f, 0x16, 0xae, 0x42,
    0xb4, 0xb6, 0xad, 0xe4, 0x7c, 0x63, 0x5b, 0x2b, 0xbd, 0xc5, 0x43, 0xa1,
    0xef, 0x3b, 0x68, 0xc5, 0x1e, 0x51, 0x9e, 0xfd, 0x24, 0xaa, 0x39, 0x76,
    0x14, 0x1f, 0xf4, 0x25, 0x2e, 0x44, 0x7f, 0xe0, 0xdc, 0x4b, 0xd6, 0x1c,
    0x7e, 0xb0, 0x6d, 0xed, 0x85, 0x7b, 0x37, 0xb6, 0xdd, 0x11, 0xac, 0xfa,
    0xf0, 0xe6, 0x4b, 0xe8, 0x45, 0x43, 0xda, 0xd7, 0x2b, 0x86, 0x5a, 0xcc,
    0x77, 0x68, 0x1c, 0xe9, 0xaf, 0x55, 0x23, 0x70, 0x99, 0x4b, 0x3b, 0x80,
    0x7b, 0xd8, 0xde, 0xae, 0x61, 0x73, 0xdd, 0xbb, 0x9d, 0xbe, 0xcc, 0x33,
    0x78, 0xed, 0x25, 0x6b, 0x36, 0x1e, 0xc0, 0xd2, 0x3d, 0xc2, 0x89, 0x07,
    0x15, 0xff, 0xa4, 0xb4, 0x67, 0xb1, 0xd8, 0x29, 0x10, 0x1f, 0x7f, 0x8a,
    0x6d, 0x75, 0xc5, 0xc6, 0x6b, 0x4f, 0xe1, 0x8e, 0x3f, 0x97, 0xb6, 0xab,
    0xaf, 0x76, 0xe5, 0x1e, 0xda, 0xa4, 0x69, 0x9e, 0xa6, 0xa6, 0xbc, 0x95,
    0xd4, 0x77, 0x3b, 0xad, 0xd4, 0x2a, 0xd3, 0x43, 0x53, 0xf6, 0xab, 0x7a,
    0x26, 0xaf, 0x96, 0x75, 0x8b, 0x7c, 0x56, 0xc7, 0xb2, 0x05, 0x94, 0x7c,
    0x5f, 0xdb, 0x66, 0xa9, 0xcb, 0xa6, 0xb9, 0x25, 0x89, 0xfc, 0x25, 0x9a,
    0x6e, 0xd2, 0x74, 0x35, 0x99, 0xdf, 0xbb, 0x1b, 0x92, 0xaa, 0xf4, 0x3c,
    0x9d, 0x9e, 0x97, 0x28, 0x97, 0xa9, 0xeb, 0xda, 0x20, 0xcf, 0x5c, 0xa1,
    0xa1, 0xda, 0xef, 0x4f, 0xb4, 0x67, 0xb6, 0xa9, 0x90, 0x27, 0x9f, 0x6a,
    0xdb, 0x28, 0xf9, 0x3e, 0xb8, 0xf9, 0x8e, 0x07, 0xd3, 0x6c, 0x5d, 0xb7,
    0x39, 0x9e, 0x1c, 0xad, 0x6f, 0xbe, 0xe7, 0x61, 0x48, 0x5a, 0x44, 0xe6,
    0x7b, 0x05, 0x4c, 0xcd, 0xf7, 0x3c, 0xdc, 0x9a, 0x9a, 0xe3, 0xaa, 0xd4,
    0x75, 0x98, 0xef, 0x29, 0xd8, 0x25, 0x9f, 0x2d, 0xa9, 0xe2, 0xd5, 0xdb,
    0xf0, 0x0e, 0xc9, 0xe7, 0x27, 0xfa, 0xa2, 0xca, 0x56, 0xea, 0xb2, 0x95,
    0xb2, 0x3e, 0x96, 0x2f, 0xd7, 0x79, 0xe6, 0x7b, 0x11, 0x1d, 0x9a, 0xf6,
    0xe8, 0x76, 0x7a, 0x70, 0xb2, 0xa4, 0x69, 0x9d, 0x5e, 0xed, 0x13, 0xb7,
    0xe8, 0xb6, 0x2c, 0xb3, 0x51, 0x41, 0x46, 0x45, 0x25, 0x00, 0x95, 0x4a,
    0x2a, 0xaf, 0x6d, 0xe8, 0x6c, 0xae, 0xed, 0x69, 0xea, 0xa8, 0xea, 0xe9,
    0xee, 0x69, 0xae, 0x6a, 0xec, 0x6c, 0x68, 0xa8, 0xea, 0x58, 0xd5, 0x54,
    0x57, 0xb5, 0xb2, 0xab, 0xa7, 0xa1, 0xb1, 0xa7, 0xab, 0xb1, 0x6b, 0x55,
    0x6d, 0x2d, 0xa5, 0xb5, 0x8d, 0x06, 0x03, 0xa1, 0x40, 0x7c, 0x2d, 0xa5,
    0xb4, 0x29, 0x6a, 0xac, 0x6d, 0x25, 0xdb, 0xda, 0xd6, 0x4a, 0xb2, 0xe3,
    0x63, 0x98, 0x3f, 0x21, 0xc8, 0xeb, 0x0c, 0x4e, 0xfa, 0xe3, 0xe1, 0x70,
    0x7c, 0x7c, 0x53, 0x60, 0x2c, 0xb0, 0xc9, 0x17, 0xf2, 0xed, 0xf6, 0x47,
    0x69, 0xed, 0x6c, 0x52, 0xb7, 0x3f, 0x1a, 0x0d, 0x47, 0x57, 0xbb, 0x47,
    0xc3, 0x93, 0xc1, 0x31, 0x77, 0x28, 0x1c, 0x77, 0xef, 0xf6, 0xc7, 0xdd,
    0x09, 0x4d, 0x77, 0x5f, 0x8f, 0x3b, 0x36, 0xea, 0x0b, 0x85, 0x50, 0xbe,
    0xeb, 0x9f, 0x2f, 0x3f, 0xe6, 0xdf, 0xe5, 0x9b, 0x0c, 0x5a, 0xeb, 0xf1,
    0x8d, 0xf9, 0x22, 0x71, 0x54, 0xe2, 0xea, 0x6e, 0x2e, 0x2f, 0x1f, 0x3c,
    0x18, 0x8a, 0x8f, 0xfb, 0xe3, 0x81, 0xd1, 0xae, 0xa0, 0x2f, 0x16, 0x83,
    0xcc, 0xbf, 0x2f, 0x30, 0xea, 0xef, 0xf2, 0x05, 0x83, 0x3b, 0x7d, 0xa3,
    0x7b, 0x7b, 0x27, 0x22, 0x41, 0x72, 0x76, 0x07, 0x62, 0xa3, 0xe1, 0x7d,
    0xfe, 0xa8, 0x7f, 0x4c, 0xe5, 0x92, 0xe8, 0x25, 0xa3, 0x17, 0xf3, 0xd5,
    0xcb, 0xb4, 0x8f, 0x6c, 0xbd, 0x7d, 0x7d, 0x54, 0xd2, 0xbb, 0xc5, 0xdd,
    0x73, 0x60, 0xd4, 0x1f, 0x89, 0x07, 0xc2, 0x21, 0xf7, 0xfe, 0xf1, 0x40,
    0xd0, 0xef, 0x1e, 0x0d, 0x86, 0x63, 0x81, 0xd0, 0x6e, 0x77, 0x24, 0x1c,
    0x8d, 0xd3, 0xe2, 0xde, 0x2d, 0xa7, 0xcb, 0x9f, 0xc0, 0x20, 0xd0, 0x51,
    0x59, 0x77, 0x51, 0x2f, 0x06, 0x18, 0x4f, 0xf4, 0xf6, 0x5c, 0x5f, 0x3c,
    0x6e, 0xf6, 0x86, 0x72, 0x7a, 0x43, 0xbb, 0xc2, 0xe7, 0x07, 0xe2, 0xe3,
    0xde, 0xa8, 0x2f, 0x14, 0x93, 0xb5, 0x8a, 0x0d, 0x64, 0xdf, 0x30, 0xd4,
    0xd5, 0x43, 0xae, 0x0d, 0x93, 0xa3, 0x7e, 0x9e, 0x0d, 0xd5, 0x47, 0xd6,
    0xa4, 0x1c, 0x53, 0xd6, 0x1b, 0x8a, 0x4c, 0xc6, 0xfb, 0x59, 0x3f, 0xa1,
    0xb6, 0x65, 0x32, 0x6e, 0xca, 0xb2, 0x4c, 0x99, 0x4c, 0xe5, 0x99, 0xa9,
    0xc1, 0xc9, 0x08, 0x37, 0x51, 0xbd, 0xc7, 0xb7, 0xcf, 0x47, 0xa2, 0x8f,
    0x8c, 0xbe, 0x5e, 0xb2, 0xf5, 0x61, 0xcc, 0x76, 0x7c, 0x6c, 0xe0, 0xcf,
    0x0d, 0x7d, 0x2c, 0xe0, 0x1c, 0x66, 0x64, 0x36, 0x38, 0x7b, 0x5f, 0xdf,
    0xb6, 0x3e, 0x2a, 0xef, 0xf3, 0x85, 0xc6, 0xa2, 0xe1, 0xc0, 0x58, 0xcd,
    0x4e, 0x73, 0x2c, 0x35, 0x89, 0x51, 0x75, 0xa8, 0x25, 0x68, 0xa5, 0xb2,
    0x33, 0x69, 0xa9, 0x91, 0xb4, 0x52, 0xe9, 0x99, 0x94, 0x78, 0x82, 0x5a,
    0xa9, 0xf2, 0x6c, 0x2a, 0xe6, 0x1c, 0xb6, 0x9e, 0xb9, 0x63, 0xda, 0x94,
    0x66, 0xaf, 0x30, 0xe8, 0x4f, 0x2a, 0xf6, 0xf9, 0x07, 0x95, 0x2d, 0xce,
    0x3e, 0x06, 0xa8, 0x72, 0x7e, 0xb2, 0xd5, 0xd3, 0xd4, 0xc7, 0x4a, 0xeb,
    0x02, 0x41, 0xcc, 0x45, 0x79, 0xe7, 0x64, 0x20, 0x38, 0xc6, 0xf5, 0xcd,
    0x36, 0xdc, 0x29, 0xaa, 0x67, 0x54, 0x19, 0xf0, 0xc7, 0x60, 0xee, 0xad,
    0xb4, 0xfc, 0xf4, 0x2a, 0x83, 0xfe, 0x78, 0x1c, 0x66, 0x17, 0x4b, 0x36,
    0x79, 0x86, 0x21, 0x98, 0xca, 0xad, 0x34, 0x3f, 0xa1, 0x34, 0x1a, 0x0e,
    0xc5, 0xfd, 0xa1, 0x78, 0x4d, 0x17, 0xd3, 0x03, 0x68, 0xac, 0x24, 0x91,
    0x35, 0xe1, 0x1f, 0x0b, 0xf8, 0x6a, 0xd8, 0xa0, 0x6b, 0x92, 0xc6, 0xd8,
    0x4a, 0x2b, 0xce, 0xac, 0xc0, 0xd6, 0x5a, 0xce, 0xd6, 0xc7, 0x8c, 0xb5,
    0x3b, 0xa7, 0xd5, 0x6e, 0x25, 0xcf, 0xe9, 0x94, 0x12, 0xc6, 0xde, 0x4a,
    0x35, 0xa7, 0xd3, 0xd1, 0xeb, 0x5c, 0x3e, 0x75, 0xbb, 0xb7, 0xd2, 0xaa,
    0xb3, 0x15, 0xd8, 0x12, 0x52, 0x45, 0xb6, 0x44, 0xfc, 0x21, 0xff, 0x58,
    0x5f, 0x20, 0x86, 0x89, 0xe0, 0x19, 0x74, 0x9f, 0xa5, 0xe0, 0x19, 0x06,
    0x95, 0xdc, 0x89, 0xd6, 0x85, 0x9d, 0xa6, 0x34, 0xe0, 0x1f, 0xf5, 0x07,
    0xf6, 0x71, 0x3d, 0x45, 0x09, 0x95, 0x70, 0xac, 0x46, 0xae, 0x60, 0xf9,
    0x70, 0xcf, 0xc0, 0x60, 0xef, 0x96, 0xcd, 0xad, 0x94, 0x3b, 0x35, 0x2f,
    0x34, 0x16, 0xc4, 0xdc, 0xe7, 0x59, 0x85, 0xeb, 0x7d, 0x2c, 0x44, 0x35,
    0x05, 0x56, 0x69, 0xbf, 0x2f, 0x3a, 0xea, 0x0f, 0x0e, 0x4d, 0x06, 0xc6,
    0x5a, 0xc9, 0x95, 0xc8, 0x98, 0x8c, 0x07, 0x82, 0x35, 0x7d, 0xe1, 0xdd,
    0xad, 0xd4, 0xd8, 0x37, 0x1a, 0x9e, 0xa8, 0x89, 0x4e, 0xc4, 0x82, 0x35,
    0x7b, 0xe0, 0x1d, 0x6a, 0xa6, 0xb9, 0x88, 0xf2, 0xd9, 0xdc, 0x71, 0x2b,
    0xd5, 0x9f, 0xa5, 0xd4, 0x4c, 0x5f, 0xdb, 0x4a, 0x75, 0x67, 0x29, 0x33,
    0xc3, 0x03, 0x9e, 0xbd, 0x99, 0x99, 0x8e, 0xf1, 0xec, 0xcd, 0xcc, 0x70,
    0x9c, 0xff, 0x7c, 0x33, 0xd6, 0xe5, 0x5c, 0xf1, 0x4f, 0x96, 0x51, 0xda,
    0x1b, 0xcf, 0xa2, 0x9d, 0x1c, 0x84, 0x69, 0x8d, 0xe5, 0x3d, 0x07, 0xe0,
    0x0f, 0x42, 0xbe, 0x60, 0xe2, 0x18, 0xeb, 0xf3, 0x4d, 0xec, 0x1c, 0xf3,
    0xd5, 0xfe, 0xff, 0xac, 0xac, 0xae, 0x95, 0x3a, 0xff, 0xe5, 0xca, 0xa6,
    0x9f, 0x99, 0xad, 0xd4, 0xf7, 0x2f, 0xd7, 0x71, 0xfa, 0x93, 0xb0, 0x95,
    0xba, 0xff, 0xe5, 0xda, 0x92, 0x12, 0xde, 0xbb, 0x5e, 0x5f, 0x6c, 0xef,
    0xd9, 0xcd, 0x60, 0x46, 0x2d, 0x67, 0x37, 0x83, 0xcd, 0xbe, 0x38, 0xf6,
    0xe9, 0xd4, 0x2d, 0x5b, 0x71, 0x96, 0x32, 0xbc, 0xfc, 0xfd, 0xbe, 0xf8,
    0x38, 0xfb, 0x9f, 0x33, 0x6b, 0x26, 0xcc, 0x7e, 0xba, 0xe3, 0x2a, 0x39,
    0x73, 0x41, 0x76, 0x97, 0x63, 0xbe, 0xe0, 0xbe, 0xc0, 0xde, 0x1a, 0x9c,
    0x5a, 0xe1, 0xb8, 0x8f, 0xa3, 0x90, 0x9a, 0x9e, 0x90, 0x8e, 0x40, 0x64,
    0xf0, 0xd3, 0x4a, 0xc5, 0xb3, 0xe8, 0xf4, 0xf2, 0x21, 0xa7, 0xf3, 0x4b,
    0x67, 0xc9, 0xdf, 0xe4, 0x9f, 0xd8, 0xa9, 0x15, 0xfc, 0x31, 0x3e, 0x5d,
    0x67, 0x53, 0x89, 0x8f, 0x87, 0xc7, 0xe0, 0x63, 0x7c, 0x13, 0x7e, 0x58,
    0x18, 0xb4, 0x16, 0xcd, 0xa2, 0x35, 0x18, 0xd8, 0x1d, 0xf2, 0xc5, 0x27,
    0xa3, 0x7e, 0x76, 0x71, 0x33, 0xb3, 0xbd, 0xe3, 0xd1, 0xf0, 0x7e, 0x14,
    0x9d, 0xd7, 0xc7, 0xf1, 0x48, 0x4d, 0x20, 0x5c, 0x63, 0x89, 0xa7, 0x5a,
    0x29, 0x47, 0x89, 0x83, 0xbe, 0xd0, 0xee, 0x1a, 0xdd, 0xdb, 0x79, 0x16,
    0x91, 0x45, 0xb3, 0xdc, 0x22, 0xee, 0x0d, 0x06, 0xfd, 0xbb, 0x7d, 0xc1,
    0x8e, 0xe8, 0xee, 0xc9, 0x09, 0x1c, 0x68, 0x16, 0xad, 0x5c, 0xab, 0x16,
    0x4e, 0xb9, 0xdd, 0xca, 0xc9, 0x27, 0x85, 0x9b, 0x27, 0x83, 0xc1, 0xfe,
    0x70, 0x00, 0x59, 0x51, 0x4b, 0x31, 0x97, 0x45, 0x63, 0xcb, 0xce, 0x3d,
    0xfe, 0xd1, 0xf8, 0x54, 0xd9, 0x60, 0x3c, 0x8a, 0xe9, 0xe6, 0xb3, 0x74,
    0xba, 0x2c, 0x71, 0x16, 0x5b, 0xbb, 0x2d, 0x07, 0xed, 0xdb, 0xc9, 0x5e,
    0x7c, 0x91, 0x45, 0x1c, 0xf5, 0xef, 0xaa, 0x39, 0xdf, 0xef, 0xdb, 0x3b,
    0xe0, 0xdf, 0x85, 0x1d, 0x16, 0x1a, 0x3d, 0x5b, 0x76, 0x9b, 0x59, 0xa9,
    0xf4, 0xe8, 0x1d, 0xd1, 0xa8, 0xef, 0x20, 0x9f, 0x5c, 0xad, 0xb3, 0x8b,
    0xdb, 0xcc, 0x1e, 0x27, 0xc5, 0x98, 0xcd, 0x7c, 0x8b, 0xac, 0x2b, 0x8c,
    0x79, 0x1b, 0x9d, 0x32, 0x51, 0x52, 0xbe, 0xde, 0x17, 0x43, 0x04, 0x15,
    0x99, 0x55, 0xd8, 0x36, 0x43, 0x88, 0xc8, 0x62, 0xa6, 0x26, 0x84, 0x6d,
    0x38, 0xb1, 0x92, 0xc2, 0x5e, 0x4c, 0xb0, 0x2f, 0x1e, 0xc6, 0xc4, 0x38,
    0x2d, 0x52, 0xd5, 0xfd, 0xe9, 0x92, 0xa9, 0x6d, 0xa8, 0xf9, 0x47, 0xd7,
    0xb3, 0x2d, 0x42, 0xd9, 0xe8, 0x34, 0x41, 0x9b, 0x69, 0x3d, 0x52, 0xe0,
    0x0d, 0x4c, 0x58, 0x96, 0x21, 0x29, 0x52, 0x1e, 0xa3, 0xc4, 0x22, 0x46,
    0x18, 0x34, 0x3a, 0x19, 0x8d, 0x72, 0x24, 0xd4, 0x73, 0xc0, 0x3f, 0x3a,
    0x29, 0x7b, 0xb9, 0xe4, 0xcc, 0x0a, 0x83, 0xfe, 0xa8, 0x72, 0x87, 0xee,
    0x33, 0xeb, 0xc5, 0x12, 0x4b, 0x2a, 0x35, 0x76, 0x4d, 0x86, 0xe4, 0x7c,
    0xd7, 0xac, 0xd3, 0xcc, 0x59, 0xb2, 0x31, 0xa4, 0x19, 0xbe, 0x8b, 0x3a,
    0x66, 0x88, 0x66, 0xbf, 0x32, 0x59, 0xef, 0x54, 0xb1, 0x83, 0x88, 0x70,
    0x26, 0xdc, 0x31, 0xd5, 0x6d, 0x6a, 0x9c, 0x59, 0xc5, 0x7e, 0x5f, 0x34,
    0x04, 0xfb, 0x5d, 0xed, 0x1e, 0xdc, 0x1b, 0x88, 0x44, 0xf8, 0x3a, 0xb3,
    0xa9, 0xb7, 0xbb, 0xd7, 0x3d, 0xb4, 0xa9, 0x5f, 0x5f, 0x69, 0xdc, 0xe4,
    0x9a, 0xe9, 0x7b, 0xc9, 0x35, 0xd3, 0x51, 0x52, 0x9a, 0xe9, 0x08, 0x29,
    0x75, 0xb0, 0x7b, 0xe3, 0x8e, 0xde, 0xcd, 0x5e, 0x5a, 0x6c, 0x8d, 0x41,
    0xab, 0xbb, 0x3a, 0xfa, 0xfa, 0x3a, 0x3b, 0xba, 0x36, 0xee, 0xf0, 0x6e,
    0xed, 0xef, 0xd9, 0xb1, 0xa9, 0xc3, 0xdb, 0xb5, 0x7e, 0x47, 0xdf, 0x96,
    0x41, 0x2f, 0xe5, 0x79, 0x07, 0x3a, 0x36, 0x0f, 0xf6, 0x6f, 0x19, 0xf0,
    0xee, 0xe8, 0xdc, 0xea, 0xed, 0x19, 0xf4, 0x0e, 0xf4, 0x74, 0x6c, 0xa2,
    0x39, 0x49, 0x29, 0xba, 0x43, 0x05, 0xa7, 0xf1, 0x9e, 0x24, 0x86, 0xc9,
    0x18, 0xc6, 0xed, 0x65, 0x18, 0x77, 0x19, 0xfb, 0x30, 0xdf, 0xe6, 0x8c,
    0xe1, 0x0d, 0x94, 0x32, 0xbc, 0x41, 0x5e, 0x74, 0x8c, 0x61, 0xdc, 0x6e,
    0x86, 0x71, 0xbb, 0xb1, 0x0f, 0xf3, 0xed, 0xc7, 0x31, 0xac, 0xae, 0x3f,
    0x20, 0x1b, 0x58, 0x2e, 0x3f, 0xfa, 0x58, 0xcc, 0xb9, 0x29, 0x92, 0xc8,
    0x6c, 0x2d, 0x44, 0x41, 0x63, 0x78, 0x1b, 0x89, 0x6d, 0x64, 0x6c, 0x03,
    0x8b, 0xab, 0x91, 0x31, 0xd2, 0x49, 0x9e, 0x91, 0xb3, 0x47, 0xf9, 0x55,
    0x23, 0xff, 0x52, 0xd4, 0x5c, 0xfe, 0x4f, 0xa8, 0x23, 0x1a, 0x1c, 0x99,
    0xcd, 0xb3, 0xe5, 0x8e, 0xcc, 0xe2, 0xb8, 0x32, 0x7c, 0xa3, 0xa3, 0xfe,
    0x58, 0xac, 0xbc, 0x16, 0xd7, 0x7d, 0x93, 0xaf, 0xb3, 0xf0, 0xf5, 0xe0,
    0x33, 0x15, 0xbf, 0x2e, 0xe8, 0xdb, 0x1d, 0x23, 0x9b, 0x6f, 0x6c, 0x0c,
    0x12, 0xd5, 0x09, 0x79, 0x99, 0xcc, 0xf0, 0x45, 0x22, 0xfa, 0x96, 0x40,
    0x29, 0xe0, 0xfd, 0xa1, 0x31, 0x72, 0x80, 0x06, 0x0f, 0x22, 0x19, 0xe3,
    0x3d, 0x4b, 0xe9, 0x89, 0xf1, 0x53, 0x51, 0x82, 0xed, 0xeb, 0x91, 0x87,
    0x97, 0xb2, 0xba, 0xa1, 0xa1, 0xde, 0x6e, 0x72, 0xee, 0x9c, 0x76, 0x47,
    0xa3, 0xbc, 0x9d, 0xd6, 0xa8, 0x53, 0x8d, 0x31, 0x66, 0xd1, 0xdb, 0xa1,
    0x6f, 0xd4, 0xce, 0x9d, 0x7a, 0xbd, 0x63, 0xfd, 0xbe, 0x00, 0xbb, 0x5a,
    0x72, 0xec, 0x64, 0x67, 0x4b, 0x69, 0xa3, 0xa6, 0x01, 0xa4, 0x60, 0xd6,
    0x11, 0x00, 0x93, 0x63, 0x34, 0xe8, 0xf7, 0x45, 0x99, 0x84, 0x63, 0x7e,
    0x4a, 0xc5, 0xbe, 0x0c, 0x61, 0x26, 0x28, 0x53, 0x33, 0x1c, 0x82, 0xa0,
    0x14, 0xc6, 0xe3, 0x0b, 0x84, 0x62, 0x52, 0x2c, 0xb9, 0x8d, 0xfe, 0x83,
    0x24, 0xc6, 0x28, 0x5b, 0x3f, 0x6b, 0xe8, 0x8f, 0x86, 0xe3, 0xe1, 0xd1,
    0x70, 0x90, 0x52, 0x74, 0x0f, 0xb2, 0xc7, 0xa6, 0xd8, 0x5b, 0x8c, 0xd2,
    0x94, 0xa0, 0x77, 0x8c, 0x32, 0xc6, 0x38, 0x50, 0x52, 0xcd, 0xd8, 0xc6,
    0x30, 0x1d, 0x29, 0xfe, 0x8b, 0x27, 0x7d, 0x41, 0xa8, 0xf8, 0xb5, 0x27,
    0x20, 0xdb, 0xae, 0xf2, 0x5a, 0xfe, 0xa8, 0xe3, 0x8f, 0x7a, 0xaa, 0xde,
    0xe5, 0x0b, 0x04, 0xfd, 0x63, 0xee, 0x78, 0xd8, 0x3d, 0x1a, 0xf5, 0xfb,
    0xe2, 0x7e, 0x77, 0x62, 0xc4, 0xe6, 0x86, 0xdb, 0x15, 0x0d, 0x4f, 0xb8,
    0xb1, 0x16, 0x51, 0xac, 0x0c, 0xa5, 0xed, 0x0a, 0x20, 0xa6, 0x0b, 0x5c,
    0xe2, 0xa7, 0x52, 0x70, 0x63, 0xc9, 0xb9, 0x5a, 0x17, 0x8e, 0x5a, 0x2e,
    0xe4, 0x4a, 0xb9, 0x84, 0x55, 0xcc, 0x3d, 0x3a, 0x9b, 0x42, 0x06, 0xd7,
    0xad, 0x4e, 0x2c, 0xb2, 0xef, 0xe6, 0xf9, 0xb0, 0xc1, 0x65, 0x50, 0x06,
    0x3e, 0xf4, 0xa5, 0x5e, 0xf3, 0x4a, 0xbd, 0x88, 0x79, 0x65, 0x0f, 0xd3,
    0xef, 0xd8, 0xb4, 0x20, 0x99, 0x37, 0xd3, 0x57, 0xcd, 0xe3, 0x4c, 0xd8,
    0x49, 0x60, 0x54, 0x1e, 0xff, 0xa6, 0x0d, 0xe5, 0x42, 0x3c, 0xa3, 0x4f,
    0x2e, 0xab, 0x50, 0x3f, 0xa2, 0x29, 0x9c, 0x29, 0x1b, 0x44, 0x24, 0x31,
    0x19, 0x93, 0x35, 0xcf, 0xbc, 0xc6, 0x53, 0x1a, 0xc4, 0xea, 0x19, 0x50,
    0x0e, 0xb8, 0x6e, 0xb5, 0x92, 0xe6, 0x90, 0x8a, 0x92, 0xa2, 0xe9, 0xcf,
    0x30, 0x64, 0xeb, 0xdd, 0xd3, 0x16, 0x3e, 0x5d, 0xca, 0x64, 0x47, 0x32,
    0x12, 0x6c, 0x8c, 0xf2, 0x93, 0x3c, 0xe6, 0x36, 0xf9, 0x10, 0xa7, 0x20,
    0x29, 0xef, 0x8f, 0xf2, 0xa1, 0xcd, 0xd3, 0x3b, 0x34, 0x11, 0x91, 0xa3,
    0x98, 0x1e, 0x49, 0xa3, 0x24, 0xec, 0x26, 0x15, 0x39, 0xd2, 0x1c, 0x2b,
    0xc1, 0xac, 0x9f, 0x9c, 0xe0, 0x8b, 0xbf, 0x6f, 0x8c, 0x43, 0x05, 0xb5,
    0x38, 0xb3, 0xad, 0x9d, 0x03, 0xaa, 0xd8, 0x49, 0x92, 0xa8, 0x1a, 0xd4,
    0x53, 0x22, 0xc9, 0xe8, 0x3b, 0x4b, 0x57, 0x78, 0x32, 0x14, 0xa7, 0xf9,
    0x10, 0xf1, 0x9a, 0x4c, 0xaf, 0x02, 0x63, 0xe0, 0x62, 0x9b, 0x11, 0xc8,
    0x51, 0x16, 0x98, 0x2d, 0x51, 0x3d, 0x72, 0x39, 0x0b, 0xc9, 0x9b, 0x8f,
    0xaa, 0x65, 0x0e, 0x64, 0x9c, 0xda, 0x3c, 0xc9, 0x21, 0x22, 0x65, 0xea,
    0xa4, 0x74, 0xf0, 0x69, 0x3a, 0x11, 0x53, 0x5a, 0xd1, 0x70, 0x04, 0xb1,
    0x7e, 0x00, 0xf5, 0x67, 0x23, 0x39, 0xe0, 0x9f, 0x08, 0xc7, 0xfd, 0x7a,
    0x02, 0x79, 0x2e, 0xb5, 0xc1, 0x39, 0x99, 0x95, 0x07, 0x92, 0xf6, 0x0c,
    0x72, 0x31, 0x13, 0xf3, 0x88, 0x31, 0xeb, 0x32, 0xdc, 0x49, 0xef, 0xc1,
    0x88, 0x9f, 0x72, 0xc6, 0xe5, 0x7d, 0x57, 0x89, 0x31, 0x08, 0xff, 0x18,
    0xe5, 0x5a, 0x45, 0xdc, 0xd2, 0x3e, 0x08, 0xe7, 0x28, 0xa1, 0x3e, 0x89,
    0x28, 0x75, 0xdc, 0x17, 0xdb, 0xcc, 0xf6, 0x66, 0x07, 0x83, 0xce, 0xf2,
    0x67, 0x57, 0x78, 0xcc, 0x8f, 0x74, 0x18, 0x7b, 0xd4, 0x08, 0x8c, 0x91,
    0x3d, 0xc0, 0x73, 0x97, 0x86, 0x00, 0x70, 0xd8, 0x87, 0x59, 0x02, 0x17,
    0xeb, 0x18, 0xe5, 0x13, 0x8d, 0x32, 0x03, 0xb1, 0x2e, 0xb5, 0xa3, 0x51,
    0x71, 0x6a, 0x20, 0xd6, 0x33, 0x11, 0x89, 0x1f, 0x44, 0xbe, 0x0e, 0x64,
    0x28, 0x65, 0xaf, 0xff, 0x20, 0x4e, 0x34, 0xea, 0x0f, 0xca, 0xeb, 0x55,
    0x39, 0x86, 0x1e, 0x9a, 0x72, 0xd7, 0xe4, 0xfb, 0x6d, 0x6f, 0x77, 0x79,
    0x6d, 0x39, 0x42, 0xfa, 0x2a, 0x0e, 0xe9, 0xab, 0x38, 0xa4, 0xaf, 0x3a,
    0xeb, 0xe5, 0x84, 0xce, 0x9b, 0x56, 0x63, 0x72, 0x41, 0x74, 0x95, 0x75,
    0xff, 0x72, 0x95, 0x59, 0x13, 0xbe, 0xbd, 0xc9, 0x67, 0x83, 0xa9, 0x13,
    0x49, 0x71, 0x68, 0x72, 0x97, 0x6f, 0x94, 0xe3, 0xf5, 0x28, 0xd9, 0xf9,
    0xa4, 0xa1, 0x9c, 0x89, 0x19, 0xa5, 0x33, 0x27, 0x2c, 0x9e, 0xd9, 0x1e,
    0x62, 0xb3, 0x71, 0xf0, 0x67, 0x8c, 0x0a, 0x42, 0xfe, 0xfd, 0x83, 0x58,
    0xd3, 0xa0, 0x1f, 0x31, 0x2e, 0x2c, 0xd7, 0x8c, 0x7b, 0xa0, 0xc5, 0xf3,
    0xee, 0x0a, 0x87, 0x3a, 0x7d, 0xf1, 0xd1, 0xf1, 0xe4, 0x13, 0x2d, 0x14,
    0x91, 0x6e, 0x20, 0xa4, 0x02, 0x4f, 0xde, 0xcc, 0xfe, 0x2e, 0xac, 0xda,
    0x6e, 0x3f, 0xcd, 0x09, 0x87, 0xac, 0x0b, 0x3c, 0x37, 0x3c, 0xe5, 0x19,
    0x0d, 0xcd, 0x9b, 0x9a, 0xee, 0xf6, 0x07, 0x7d, 0x07, 0x21, 0xce, 0x36,
    0xc5, 0xa6, 0x0d, 0xa4, 0x86, 0x43, 0xeb, 0x82, 0x93, 0x58, 0xee, 0x2c,
    0x54, 0xcf, 0xc7, 0xb0, 0xf4, 0xb3, 0x66, 0x4a, 0xf5, 0x82, 0x52, 0x90,
    0xe2, 0x43, 0x2c, 0x97, 0xe7, 0x78, 0xba, 0xd3, 0xc9, 0x60, 0xa1, 0xe6,
    0xe7, 0x30, 0x9f, 0x7c, 0xee, 0x5a, 0x70, 0x9a, 0x55, 0xa6, 0xc2, 0xd3,
    0x2d, 0x16, 0xc6, 0x81, 0x1c, 0xcb, 0x63, 0xda, 0x74, 0x4e, 0xab, 0x3d,
    0x23, 0x59, 0x76, 0xd1, 0xd8, 0xd4, 0xe1, 0xfd, 0xec, 0xbe, 0x72, 0x23,
    0x38, 0xdb, 0xa6, 0xf7, 0xa7, 0x68, 0x16, 0xe1, 0x60, 0xdc, 0x1f, 0xf1,
    0xee, 0x0f, 0x53, 0xc1, 0x94, 0xbc, 0xe4, 0xc2, 0x53, 0x7a, 0x44, 0x46,
    0x14, 0x63, 0xfe, 0x03, 0x94, 0x1a, 0x89, 0x86, 0xc7, 0x26, 0xf9, 0x64,
    0x42, 0x1f, 0x28, 0x2d, 0x6a, 0xc6, 0x69, 0xf9, 0x51, 0xff, 0x6e, 0x7e,
    0xf2, 0x15, 0x9d, 0x16, 0x47, 0xa5, 0x44, 0xe5, 0x54, 0x52, 0x86, 0xa2,
    0xb2, 0xd7, 0xf3, 0xa3, 0x38, 0xd0, 0xfc, 0xb1, 0x78, 0x72, 0xe9, 0xfa,
    0xa3, 0x81, 0x70, 0x34, 0x80, 0x2d, 0x61, 0x8b, 0x4e, 0x86, 0x28, 0xd5,
    0x7c, 0xa6, 0x9f, 0x16, 0x1b, 0x1d, 0xf7, 0x8f, 0x4d, 0x06, 0xb1, 0xc9,
    0x62, 0x3c, 0xc3, 0x45, 0xfc, 0xa9, 0x1e, 0x8d, 0x8f, 0xfb, 0xc6, 0xdc,
    0xbd, 0x5b, 0xdc, 0x7e, 0xf3, 0x9e, 0x45, 0x59, 0x88, 0x47, 0x03, 0xbe,
    0xa0, 0x76, 0x2c, 0x73, 0x54, 0x6a, 0x47, 0x48, 0x25, 0x6d, 0x31, 0x6c,
    0xae, 0x6c, 0x7c, 0x98, 0xfd, 0x92, 0x9e, 0x20, 0x13, 0x02, 0x5e, 0xc7,
    0x4d, 0xbc, 0x8d, 0xe7, 0x72, 0x42, 0x07, 0x17, 0x93, 0xd8, 0xcc, 0xb6,
    0x58, 0x74, 0x94, 0x1c, 0xb1, 0xb8, 0x8f, 0xa7, 0x59, 0x12, 0x56, 0x45,
    0xc5, 0x92, 0x8d, 0x87, 0x23, 0x32, 0x69, 0x8f, 0x81, 0x43, 0x3f, 0xcd,
    0x74, 0x4a, 0x7c, 0x3c, 0x80, 0xe8, 0x88, 0xd2, 0xe2, 0x61, 0xed, 0xa0,
    0xd2, 0xe3, 0x09, 0x9f, 0xee, 0x8a, 0xcf, 0x74, 0x4b, 0xf6, 0x38, 0xf7,
    0x64, 0xde, 0x64, 0x68, 0xb6, 0xc5, 0x9a, 0x3f, 0x4d, 0x6c, 0x59, 0x92,
    0xc2, 0xc9, 0xd0, 0x69, 0x66, 0xdc, 0xb1, 0x4f, 0x3a, 0x9f, 0x54, 0x49,
    0xb6, 0xec, 0xa2, 0xcf, 0x88, 0x8f, 0x7e, 0xb4, 0xbb, 0xf9, 0xc3, 0x1e,
    0xce, 0xc4, 0xf4, 0x79, 0x56, 0x7b, 0xb0, 0x8e, 0x9e, 0x15, 0x1e, 0x6c,
    0xfb, 0x48, 0x20, 0x28, 0x8f, 0xd2, 0xaa, 0x09, 0xcc, 0x00, 0x32, 0xa2,
    0x7e, 0xc4, 0x3a, 0x31, 0x3f, 0x32, 0xe1, 0xdd, 0xaa, 0x30, 0xf7, 0x08,
    0x4c, 0x26, 0x27, 0x62, 0x9e, 0xd5, 0xbb, 0x10, 0x7e, 0xf8, 0x57, 0x78,
    0x26, 0x02, 0xa1, 0x2a, 0x5f, 0x24, 0xe0, 0x59, 0x5d, 0xdf, 0xb8, 0xc2,
    0x13, 0x1b, 0xf7, 0x55, 0xd5, 0xa1, 0x90, 0x6f, 0x95, 0x6f, 0xac, 0xae,
    0xd9, 0xb7, 0xaa, 0xb6, 0x71, 0x65, 0xed, 0xce, 0x55, 0x2d, 0x2d, 0x63,
    0xb5, 0x8d, 0xcd, 0xf5, 0xfe, 0xc6, 0x96, 0x55, 0xa3, 0x75, 0xb5, 0x2d,
    0xbe, 0x55, 0x4d, 0x3b, 0x77, 0xad, 0xda, 0xd5, 0x52, 0x37, 0xc6, 0xb5,
    0xc2, 0x50, 0x62, 0x68, 0x0e, 0x85, 0x9a, 0xab, 0xeb, 0x6a, 0xab, 0x5b,
    0xaa, 0x10, 0x9c, 0x78, 0x3e, 0x42, 0x46, 0xbd, 0x78, 0xc5, 0x58, 0x68,
    0x2f, 0x30, 0x0a, 0xe2, 0x05, 0xfe, 0x82, 0x34, 0x33, 0x29, 0x90, 0x6c,
    0x2a, 0xc8, 0x50, 0x49, 0x5b, 0xc1, 0xc5, 0x32, 0xaf, 0x1a, 0x89, 0xc2,
    0x2a, 0xa3, 0xc6, 0x38, 0x26, 0xec, 0xe9, 0xd7, 0x1b, 0x05, 0x59, 0x49,
    0xd6, 0x99, 0x64, 0x8b, 0xcc, 0x42, 0x7b, 0x65, 0xa1, 0x84, 0x7c, 0x91,
    0x64, 0xcb, 0xd3, 0xed, 0xe0, 0xdd, 0x46, 0x03, 0xeb, 0x88, 0xc2, 0x35,
    0x26, 0xd3, 0x98, 0x54, 0x2c, 0x4e, 0xb2, 0x25, 0x46, 0x1d, 0xd8, 0x85,
    0xf6, 0x6e, 0xe7, 0x21, 0xf9, 0xef, 0x58, 0xec, 0x74, 0x03, 0x7a, 0x57,
    0x8c, 0x3f, 0xd5, 0x99, 0x4a, 0xa5, 0x4a, 0x50, 0xcd, 0x51, 0xaa, 0x46,
    0xb9, 0xd3, 0x29, 0xb5, 0x8c, 0x62, 0x5d, 0x8f, 0x81, 0xcc, 0xf9, 0x2a,
    0xd3, 0x26, 0x33, 0x65, 0xb6, 0xad, 0xd8, 0x9a, 0x7f, 0x45, 0xb2, 0xc9,
    0x6b, 0x54, 0xf7, 0x53, 0x0a, 0x6c, 0x05, 0x1f, 0x2d, 0xe8, 0x2f, 0xd8,
    0x54, 0x90, 0x5e, 0xb0, 0xc9, 0x9c, 0x95, 0x30, 0x46, 0x54, 0x9d, 0x9c,
    0xa4, 0x70, 0xc1, 0xce, 0x82, 0x81, 0xa9, 0xc9, 0x9d, 0x66, 0x92, 0x87,
    0xdf, 0x6f, 0x4d, 0xac, 0x37, 0x13, 0x0e, 0x28, 0x0e, 0x15, 0x04, 0x50,
    0x73, 0x66, 0xb2, 0xd5, 0xcb, 0x8c, 0x5a, 0xa9, 0x58, 0x58, 0x51, 0xb8,
    0xa4, 0x70, 0x69, 0x52, 0x7e, 0x65, 0x92, 0xbd, 0xca, 0xec, 0x98, 0x28,
    0x38, 0xa7, 0x20, 0xb7, 0x60, 0x7f, 0x41, 0xab, 0x59, 0xa3, 0x12, 0xd9,
    0xb1, 0x64, 0xbd, 0x10, 0xc9, 0x8a, 0x32, 0x0b, 0xdd, 0x85, 0x25, 0x85,
    0x95, 0x85, 0x8b, 0x0a, 0x8b, 0x0b, 0x57, 0x14, 0x7a, 0x0a, 0x4b, 0x0b,
    0x97, 0x15, 0x96, 0x15, 0x2e, 0x17, 0x36, 0x91, 0x6e, 0x14, 0x09, 0xc3,
    0x30, 0x84, 0xb1, 0xe6, 0xd0, 0x21, 0xfb, 0xe1, 0xaa, 0xb5, 0xe2, 0xc6,
    0x15, 0x42, 0x3c, 0x0d, 0xbc, 0x05, 0x3c, 0x51, 0x25, 0xc4, 0x3b, 0xc0,
    0x55, 0xd5, 0x42, 0x9c, 0x58, 0x2e, 0xc4, 0xa1, 0x1a, 0xd0, 0x5a, 0x82,
    0xb6, 0x23, 0x47, 0xe4, 0x9c, 0x7b, 0xf9, 0x21, 0xfb, 0xa9, 0xda, 0xf5,
    0xe2, 0xaa, 0x3a, 0x68, 0xd6, 0xf1, 0x0f, 0x15, 0xa4, 0x42, 0xba, 0x01,
    0xf5, 0xfc, 0xa8, 0x8e, 0x32, 0x04, 0xa5, 0x8b, 0x99, 0x7f, 0x1b, 0x51,
    0xe6, 0xfd, 0x3a, 0x22, 0xe1, 0xd8, 0x0e, 0xee, 0x89, 0xe6, 0x0f, 0x1d,
    0x4a, 0x23, 0x31, 0x0d, 0x94, 0x22, 0xd2, 0x72, 0xf5, 0x3b, 0x22, 0xfc,
    0x5f, 0x1f, 0xb7, 0x53, 0xbf, 0x49, 0x1c, 0x69, 0x10, 0xf6, 0xc3, 0x8d,
    0x42, 0x1c, 0xab, 0x17, 0xe2, 0x46, 0xe0, 0x18, 0xf8, 0x67, 0x81, 0xd7,
    0x80, 0xeb, 0x9a, 0xc8, 0x2e, 0x52, 0xe7, 0xeb, 0x12, 0x43, 0x28, 0x71,
    0xc7, 0xca, 0x61, 0x71, 0x62, 0xa5, 0xb0, 0x1f, 0x5b, 0x25, 0xc4, 0x1b,
    0x4d, 0x42, 0xbc, 0xc2, 0x00, 0x7f, 0x5d, 0xb3, 0x10, 0x47, 0x9a, 0xc9,
    0x26, 0xc4, 0xa2, 0x9b, 0xdc, 0x42, 0xfe, 0x3f, 0x7a, 0xf9, 0x11, 0xfb,
    0xfb, 0xcd, 0x63, 0x39, 0xaf, 0x36, 0x63, 0x10, 0xc2, 0x2d, 0x65, 0x7e,
    0xc8, 0x5e, 0x6a, 0xd9, 0x95, 0x73, 0x57, 0x0b, 0xff, 0xc2, 0x01, 0xff,
    0x23, 0x2a, 0x71, 0xa3, 0x5b, 0xd0, 0x6e, 0x8c, 0xee, 0x54, 0x0b, 0xab,
    0x2d, 0x31, 0x20, 0x18, 0x47, 0x43, 0xd7, 0xad, 0x0e, 0x88, 0x47, 0x57,
    0xa3, 0x42, 0x47, 0x05, 0x26, 0x06, 0xc2, 0x3d, 0x10, 0xbe, 0xb4, 0x66,
    0xaf, 0xf8, 0xdb, 0x6a, 0xb4, 0xd8, 0x26, 0xc4, 0xcf, 0x41, 0xef, 0x02,
    0x7d, 0x6b, 0x0d, 0xc6, 0x6d, 0x5c, 0x2f, 0x90, 0xfd, 0xec, 0xf6, 0x1b,
    0x84, 0xf8, 0xc1, 0x76, 0xc8, 0xb6, 0x53, 0x7a, 0xfa, 0xdc, 0xe5, 0x86,
    0x70, 0x25, 0xfe, 0xe3, 0x4a, 0x5c, 0x11, 0x1e, 0xc1, 0xb9, 0xe2, 0xb0,
    0xfb, 0xd1, 0xb5, 0x86, 0xf1, 0x4e, 0xa7, 0x30, 0x7e, 0xd0, 0x2d, 0x32,
    0x5e, 0x01, 0x7f, 0xaa, 0x4b, 0x18, 0xc7, 0xd7, 0xd9, 0x8c, 0xe3, 0x1d,
    0xe9, 0xc6, 0xa3, 0xe7, 0x47, 0xed, 0x8f, 0xf6, 0xda, 0xc5, 0xb3, 0x3d,
    0xb6, 0x43, 0xee, 0xbb, 0xda, 0xc5, 0x21, 0xf7, 0x5b, 0xed, 0x86, 0x38,
    0xd2, 0x87, 0xe6, 0x36, 0x09, 0x71, 0x7c, 0x8b, 0x10, 0xa7, 0xbc, 0x98,
    0xa6, 0x4e, 0x21, 0x5e, 0x05, 0xde, 0x5b, 0x8b, 0xc1, 0x9f, 0x6f, 0x88,
    0x47, 0xb7, 0xa2, 0x5b, 0x5b, 0xd1, 0x13, 0xd1, 0x28, 0xae, 0x16, 0x18,
    0xcf, 0xf1, 0x11, 0x71, 0x85, 0xc1, 0xef, 0x23, 0xbc, 0x3a, 0x82, 0x51,
    0x18, 0xea, 0x1f, 0x34, 0x7d, 0x82, 0xbb, 0x79, 0xff, 0xf6, 0x6b, 0xb1,
    0xf8, 0x17, 0x62, 0x02, 0x2f, 0xb4, 0x53, 0x26, 0x35, 0x17, 0x89, 0x22,
    0x91, 0x21, 0x32, 0x6e, 0x14, 0x87, 0x0f, 0xd9, 0xaf, 0xda, 0xc9, 0x1a,
    0x77, 0xec, 0x44, 0x1f, 0x8f, 0xf3, 0xc7, 0x2b, 0x3b, 0x45, 0xfa, 0xb3,
    0x1f, 0x12, 0xe9, 0xc7, 0x76, 0x88, 0xb4, 0xbf, 0x6d, 0x17, 0x69, 0xc7,
    0x7d, 0x22, 0xed, 0xba, 0x8b, 0xc4, 0x27, 0x0c, 0x92, 0x28, 0x70, 0x18,
    0x76, 0xc1, 0x6f, 0xb4, 0xf0, 0x37, 0xda, 0xbf, 0xe8, 0x57, 0xf4, 0x5d,
    0x4d, 0xf9, 0x07, 0xf2, 0xf8, 0x3b, 0xf0, 0x39, 0xa0, 0x79, 0x16, 0xbe,
    0xc4, 0xc2, 0x2f, 0x3b, 0x4f, 0xe9, 0x36, 0x59, 0x64, 0x9d, 0x16, 0x7e,
    0x93, 0xce, 0xdf, 0xaa, 0xe9, 0x4e, 0x4b, 0xde, 0x5e, 0x0b, 0xbf, 0x4f,
    0xe7, 0x5f, 0xae, 0x65, 0x9f, 0x02, 0xbd, 0x57, 0xcb, 0x1e, 0xb5, 0xc8,
    0x9e, 0xd7, 0xb2, 0x6f, 0x58, 0x64, 0x3f, 0xb3, 0xd4, 0xf3, 0xb6, 0xce,
    0x7f, 0x4f, 0xd3, 0xd4, 0x01, 0x45, 0xf3, 0x35, 0x5d, 0xac, 0x69, 0xa3,
    0xa6, 0x1d, 0x9a, 0x6e, 0x1e, 0x50, 0xdf, 0xfd, 0x73, 0x1d, 0x3b, 0xc0,
    0xef, 0x19, 0x48, 0xd6, 0xb9, 0xdf, 0xc2, 0x5f, 0xa1, 0xf5, 0xaf, 0xd7,
    0xf4, 0x88, 0xa6, 0x8f, 0x83, 0x5e, 0x38, 0x94, 0x7c, 0xef, 0x80, 0xf4,
    0xbb, 0x04, 0x5e, 0xc8, 0xda, 0x41, 0xbb, 0x87, 0xd4, 0x7b, 0x06, 0x7d,
    0xa0, 0xe3, 0x43, 0x34, 0xe5, 0x2f, 0x3e, 0x2d, 0x7d, 0x78, 0x28, 0x59,
    0x07, 0xff, 0xf1, 0xfb, 0x94, 0x87, 0x86, 0xd4, 0xbf, 0x2b, 0x67, 0x7a,
    0xeb, 0x34, 0xfd, 0xe3, 0x3a, 0x6d, 0x96, 0x91, 0xbf, 0x7f, 0x36, 0xa4,
    0x7e, 0xab, 0xeb, 0x18, 0xe8, 0xb3, 0x3a, 0xdf, 0x7c, 0x0f, 0xe5, 0x7c,
    0x9d, 0xff, 0xda, 0x34, 0x39, 0xff, 0x6e, 0xe1, 0x2b, 0x90, 0x9d, 0x9a,
    0x26, 0xe7, 0xdf, 0x32, 0x7c, 0x03, 0xb2, 0xf7, 0xa7, 0xc9, 0xf9, 0xf7,
    0x0d, 0xdf, 0x81, 0xcc, 0x3e, 0x3c, 0xb5, 0x3f, 0xeb, 0x87, 0xd5, 0x7b,
    0x9d, 0x66, 0x7f, 0xf8, 0x3d, 0x8b, 0xc2, 0x61, 0xf5, 0x3b, 0x10, 0xcd,
    0xc3, 0xea, 0xb7, 0x1f, 0x6b, 0x87, 0xd5, 0xef, 0x3f, 0xb6, 0x0f, 0xab,
    0x7f, 0x43, 0xe6, 0x1e, 0x56, 0xbf, 0x03, 0x59, 0x31, 0xac, 0x7e, 0x23,
    0x91, 0xfb, 0xc7, 0xff, 0x5e, 0x82, 0xff, 0x89, 0xf0, 0x05, 0xc3, 0x53,
    0xdb, 0xbd, 0x46, 0x8f, 0x6b, 0x6c, 0x5a, 0xbb, 0x87, 0x86, 0xa7, 0xce,
    0x03, 0xff, 0x26, 0x5b, 0x64, 0x58, 0xfd, 0x1e, 0xdb, 0x81, 0xe1, 0xe4,
    0xef, 0x0a, 0x5a, 0xdf, 0xc7, 0x31, 0x7f, 0x03, 0x98, 0xcb, 0x98, 0xbf,
    0x03, 0xcc, 0xeb, 0xe6, 0x23, 0xf5, 0x5b, 0xc0, 0xfc, 0xce, 0x87, 0xf9,
    0x7b, 0xc0, 0xfc, 0x7e, 0x89, 0xf9, 0x9b, 0xc0, 0x3c, 0x3e, 0xf3, 0x77,
    0x81, 0x85, 0x5b, 0x95, 0xe5, 0xdf, 0x06, 0xb6, 0xb9, 0xd5, 0x6f, 0xe6,
    0xf1, 0x6f, 0x59, 0x08, 0xa7, 0xfa, 0x5d, 0x4b, 0xfe, 0xcd, 0x0f, 0xc3,
    0xad, 0xda, 0xe2, 0xdf, 0x0e, 0xb6, 0xbb, 0xd5, 0xbb, 0x96, 0xbc, 0xdf,
    0xf8, 0x65, 0x17, 0xae, 0x87, 0x7f, 0x0b, 0xc5, 0xe1, 0xd6, 0xef, 0xe9,
    0xf0, 0x6f, 0x62, 0x38, 0xd5, 0xef, 0x64, 0xb0, 0xdd, 0xa4, 0xb8, 0xd5,
    0xfb, 0x34, 0xfc, 0x9b, 0x29, 0x6c, 0x04, 0xdc, 0x6f, 0xfe, 0xfd, 0xe2,
    0xff, 0x07, 0xf7, 0x0e, 0xe8, 0x14, 0xf8, 0x58, 0x00, 0x00
};

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 STATICMETHOD (nanoTime,                 "nanoTime",                 "()J")

DECLARE_JNI_CLASS (JavaLangSystem, "java/lang/System")
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 METHOD (openMidiInputPortWithID,                  "openMidiInputPortWithID",                  "(IIJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \
 METHOD (openMidiOutputPortWithID,                 "openMidiOutputPortWithID",                 "(II)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;")

DECLARE_JNI_CLASS_WITH_BYTECODE (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 24, javaMidiByteCode)
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 FIELD (deviceId,  "deviceId",  "I") \
 FIELD (portIndex, "portIndex", "I") \
 FIELD (type,      "type",      "I") \

DECLARE_JNI_CLASS_WITH_BYTECODE (JavaPortPath, "com/rmsl/juce/JuceMidiSupport$PortPath", 24, javaMidiByteCode)
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 METHOD (constructor, "<init>", "(J)V") \

DECLARE_JNI_CLASS_WITH_BYTECODE (NativeMidiReceiver, "com/rmsl/juce/JuceMidiSupport$NativeMidiReceiver", 24, javaMidiByteCode)
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 METHOD (send,         "send",   "([BIIJ)V") \

DECLARE_JNI_CLASS (JavaMidiReceiver, "android/media/midi/MidiReceiver")
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 FIELD (name,               "name",             "Ljava/lang/String;") \
 FIELD (manufacturer,       "manufacturer",     "Ljava/lang/String;") \
 FIELD (product,            "product",          "Ljava/lang/String;") \
 FIELD (serialNumber,       "serialNumber",     "Ljava/lang/String;") \
 FIELD (id,                 "id",               "I") \
 FIELD (transport,          "transport",        "I") \
 FIELD (type,               "type",             "I") \
 FIELD (dst,                "dst",              "Ljava/util/ArrayList;") \
 FIELD (src,                "src",              "Ljava/util/ArrayList;")

DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiDeviceInfo, "com/rmsl/juce/JuceMidiSupport$JuceMidiDeviceInfo", 24, javaMidiByteCode)
#undef JNI_CLASS_MEMBERS

struct AndroidMidiHelpers
{
    static constexpr int TRANSPORT_BYTESTREAM = 1;
    static constexpr int TRANSPORT_UMP = 2;

    static jlong midiClockMillis (JNIEnv* env)
    {
        const auto nanos = env->CallStaticLongMethod (JavaLangSystem, JavaLangSystem.nanoTime);
        return nanos / (jlong) 1e6;
    }

    /*  When dealing in UMP, the system always sends/receives UMP packets in network order, i.e.
        big-endian.

        This function will ensure the correct byte ordering for a range of bytes that are about
        to cross the API boundary to/from android.
    */
    static void swapByteOrderOfWordsIfNecessary (Span<std::byte> bytes)
    {
        if ((bytes.size() % 4) != 0)
        {
            // We're expecting to receive 32-bit words, but the system hasn't given us a whole
            // number of words.
            jassertfalse;
            return;
        }

       #if JUCE_LITTLE_ENDIAN
        // Words are in network order (big endian)
        for (auto i = decltype (bytes.size()) {}; i < bytes.size(); i += sizeof (uint32_t))
            std::reverse (bytes.data() + i, bytes.data() + i + sizeof (uint32_t));
       #endif
    }

    /*  The device info is all out-of-band. These are properties of the device that we can fetch
        without needing to open a connection to the device.
        This differs from a ump::Endpoint, which requires talking to the device in order to
        retrieve some of the information.
        We can still use this info to create a stand-in endpoint for a device that's only aware of
        MIDI 1.0.
    */
    struct StaticDeviceInfo
    {
        String name;                    ///< PROPERTY_NAME
        String manufacturer;            ///< PROPERTY_MANUFACTURER
        String product;                 ///< PROPERTY_PRODUCT
        String serialNumber;            ///< PROPERTY_SERIAL_NUMBER
        std::vector<String> dst;        ///< input port names + port count
        std::vector<String> src;        ///< output port names + port count
        jint id;                        ///< device id
        jint transport;                 ///< transport, bytestream or ump
        jint type;                      ///< type, virtual/usb/bluetooth

        /*  This returns the name used by old JUCE versions to identify a specific port */
        String getLegacyId (size_t portIndex, ump::IOKind direction) const
        {
            jassert (portIndex < (direction == ump::IOKind::dst ? dst.size() : src.size()));
            return String { ((id * 128) + jmin ((int) portIndex, 127)) * (direction == ump::IOKind::dst ? -1 : 1) };
        }

        std::array<String, 16> getIds (ump::IOKind dir) const
        {
            auto& names = dir == ump::IOKind::src ? src : dst;
            jassert (names.size() <= 16);

            std::array<String, 16> result;

            for (size_t i = 0; i < names.size(); ++i)
                result[i] = getLegacyId (i, dir);

            return result;
        }

        /*  Result does *not* have an EndpointId set */
        ump::Endpoint getEquivalentBytestreamEndpoint() const
        {
            // This isn't a bytestream device!
            jassert (transport == TRANSPORT_BYTESTREAM);

            size_t numBlocks{};
            std::array<ump::Block, 32> blocks;

            for (auto [index, portName] : enumerate (dst, uint8_t{}))
            {
                blocks[numBlocks++] = ump::Block{}.withEnabled (true)
                                                  .withDirection (ump::BlockDirection::receiver)
                                                  .withUiHint (ump::BlockUiHint::receiver)
                                                  .withFirstGroup (index)
                                                  .withNumGroups (1)
                                                  .withMIDI1ProxyKind (ump::BlockMIDI1ProxyKind::unrestrictedBandwidth)
                                                  .withName (portName.isNotEmpty() ? portName : "Input Port " + String (index + 1));
            }

            for (auto [index, portName] : enumerate (src, uint8_t{}))
            {
                blocks[numBlocks++] = ump::Block{}.withEnabled (true)
                                                  .withDirection (ump::BlockDirection::sender)
                                                  .withUiHint (ump::BlockUiHint::sender)
                                                  .withFirstGroup (index)
                                                  .withNumGroups (1)
                                                  .withMIDI1ProxyKind (ump::BlockMIDI1ProxyKind::unrestrictedBandwidth)
                                                  .withName (portName.isNotEmpty() ? portName : "Output Port " + String (index + 1));
            }

            return ump::Endpoint{}.withName (name)
                                  .withMidi1Support (true)
                                  .withStaticBlocks (true)
                                  .withProductInstanceId (serialNumber)
                                  .withProtocol (ump::PacketProtocol::MIDI_1_0)
                                  .withBlocks ({ blocks.data(), numBlocks });
        }

        ump::StaticDeviceInfo getUmpStaticDeviceInfo() const
        {
            return ump::StaticDeviceInfo{}.withName (name)
                                          .withManufacturer (manufacturer)
                                          .withProduct (product)
                                          .withTransport (transport == TRANSPORT_UMP ? ump::Transport::ump
                                                                                     : ump::Transport::bytestream)
                                          .withHasSource (! src.empty())
                                          .withHasDestination (! dst.empty())
                                          .withLegacyIdentifiersSrc (getIds (ump::IOKind::src))
                                          .withLegacyIdentifiersDst (getIds (ump::IOKind::dst));
        }
    };

    struct PortPath
    {
        int deviceID;
        int portIndex;
        int type;
    };

    class MidiReceiver
    {
    public:
        MidiReceiver() = default;
        explicit MidiReceiver (GlobalRef g) : port (g) {}

        void send (jbyteArray byteArray, jint len)
        {
            getEnv()->CallVoidMethod (port, JavaMidiReceiver.send, byteArray, (jint) 0, len, (jlong) 0);
        }

        bool operator== (const MidiReceiver& other) const
        {
            return port == other.port;
        }

        bool operator!= (const MidiReceiver& other) const
        {
            return ! operator== (other);
        }

        bool operator< (const MidiReceiver& other) const
        {
            return port < other.port;
        }

        GlobalRef get() const { return port; }

    private:
        GlobalRef port;
    };

    /** Very low-level RAII wrapper over JUCE's Java MIDI port.
        For simplicity we use the same interface for sources and destinations, so some functions
        won't work depending on the actual type of the backing port.
        This may deal in either bytestream or UMP messages depending on the result of isUMP().
    */
    class MidiPort
    {
    public:
        MidiPort() = default;
        explicit MidiPort (GlobalRef p) : port (p) {}

        MidiPort (MidiPort&& other) noexcept
            : port (std::exchange (other.port, {}))
        {
        }

        MidiPort& operator= (MidiPort&& other) noexcept
        {
            std::swap (other.port, port);
            return *this;
        }

        ~MidiPort() noexcept
        {
            if (port != nullptr)
                getEnv()->CallVoidMethod (port, JuceMidiPort.close);
        }

        void start()
        {
            jassert (port != nullptr);
            getEnv()->CallVoidMethod (port, JuceMidiPort.start);
        }

        void stop()
        {
            jassert (port != nullptr);
            getEnv()->CallVoidMethod (port, JuceMidiPort.stop);
        }

        bool isActive() const
        {
            jassert (port != nullptr);
            return getEnv()->CallBooleanMethod (port, JuceMidiPort.isActive);
        }

        void sendMidi (jbyteArray byteArray, jint len)
        {
            jassert (port != nullptr);
            getEnv()->CallVoidMethod (port, JavaMidiReceiver.send, byteArray, (jint) 0, len, (jlong) 0);
        }

        std::optional<PortPath> getPortPath() const
        {
            auto* env = getEnv();
            const LocalRef<jobject> result { env->CallObjectMethod (port, JuceMidiPort.getPortPath) };

            if (result == nullptr)
                return {};

            return PortPath
            {
                env->GetIntField (result, JavaPortPath.deviceId),
                env->GetIntField (result, JavaPortPath.portIndex),
                env->GetIntField (result, JavaPortPath.type),
            };
        }

        bool operator== (std::nullptr_t) const { return port == nullptr; }
        bool operator!= (std::nullptr_t other) const { return ! operator== (other); }

        bool operator== (const MidiPort& other) const { return port == other.port; }
        bool operator!= (const MidiPort& other) const { return port != other.port; }

        bool operator< (const MidiPort& other) const
        {
            return port.get() < other.port.get();
        }

        explicit operator bool() const { return operator!= (nullptr); }

        GlobalRef get() const { return port; }

    private:
        GlobalRef port;

       #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
        METHOD (start,         "start",            "()V") \
        METHOD (stop,          "stop",             "()V") \
        METHOD (close,         "close",            "()V") \
        METHOD (isActive,      "isActive",         "()Z") \
        METHOD (send,          "send",             "([BIIJ)V") \
        METHOD (getPortPath,   "getPortPath",      "()Lcom/rmsl/juce/JuceMidiSupport$PortPath;") \

        DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 24, javaMidiByteCode)
       #undef JNI_CLASS_MEMBERS
    };

    struct DeviceManagerListener
    {
        virtual ~DeviceManagerListener() = default;
        virtual void deviceAdded (const StaticDeviceInfo&) = 0;
        virtual void deviceRemoved (int) = 0;
    };

    /*  Dispatches device-change events onto the main thread */
    class DeviceListener : private AsyncUpdater
    {
    public:
        ~DeviceListener() override
        {
            cancelPendingUpdate();
        }

        void addListener (DeviceManagerListener& l)
        {
            listeners.add (&l);
        }

        void removeListener (DeviceManagerListener& l)
        {
            listeners.remove (&l);
        }

        void deviceAdded (const StaticDeviceInfo& info)
        {
            {
                const std::scoped_lock lock { mutex };
                events.push_back (info);
            }

            triggerAsyncUpdate();
        }

        void deviceRemoved (int id)
        {
            {
                const std::scoped_lock lock { mutex };
                events.push_back (id);
            }

            triggerAsyncUpdate();
        }

    private:
        void handleAsyncUpdate() override
        {
            const auto copy = std::invoke ([&]
            {
                const std::scoped_lock lock { mutex };
                auto result = events;
                events.clear();
                return result;
            });

            for (const auto& item : copy)
            {
                if (auto* x = std::get_if<StaticDeviceInfo> (&item))
                    listeners.call ([&] (auto& l) { l.deviceAdded (*x); });

                if (auto* x = std::get_if<int> (&item))
                    listeners.call ([&] (auto& l) { l.deviceRemoved (*x); });
            }
        }

        using Event = std::variant<StaticDeviceInfo, int>;
        std::vector<Event> events;
        std::mutex mutex;

        ListenerList<DeviceManagerListener> listeners;
    };

    /** Provides notifications when a MIDI device is added/removed, and allows creating MidiPorts
        referring to known device ports.
    */
    class DeviceManager
    {
    public:
        static std::shared_ptr<DeviceManager> getSingleton()
        {
            static std::weak_ptr<DeviceManager> weak;

            if (auto strong = weak.lock())
                return strong;

            LocalRef<jobject> manager { getEnv()->CallStaticObjectMethod (JuceMidiSupport,
                                                                          JuceMidiSupport.getAndroidMidiDeviceManager,
                                                                          getAppContext().get()) };

            if (manager == nullptr)
                return {};

            std::shared_ptr<DeviceManager> strong (new DeviceManager { GlobalRef { std::move (manager) } });
            weak = strong;
            return strong;
        }

        MidiPort openMidiInputPortWithID (jint deviceID, jint portIndex, jlong host) const
        {
            return MidiPort { GlobalRef { LocalRef<jobject> (getEnv()->CallObjectMethod (deviceManager,
                                                                                         MidiDeviceManager.openMidiInputPortWithID,
                                                                                         deviceID,
                                                                                         portIndex,
                                                                                         host)) } };
        }

        MidiPort openMidiOutputPortWithID (jint deviceID, jint portIndex) const
        {
            return MidiPort { GlobalRef { LocalRef<jobject> (getEnv()->CallObjectMethod (deviceManager,
                                                                                         MidiDeviceManager.openMidiOutputPortWithID,
                                                                                         deviceID,
                                                                                         portIndex)) } };
        }

        void addListener (DeviceManagerListener& l)
        {
            listener.addListener (l);
        }

        void removeListener (DeviceManagerListener& l)
        {
            listener.removeListener (l);
        }

    private:
        explicit DeviceManager (GlobalRef g)
            : deviceManager (g)
        {
        }

        static std::vector<String> arrayListStringToVector (JNIEnv* env, jobject obj)
        {
            const auto size = env->CallIntMethod (obj, JavaArrayList.size);
            std::vector<String> result;
            result.reserve ((size_t) size);

            for (jint i = 0; i < size; ++i)
                result.push_back (juceString ((jstring) env->CallObjectMethod (obj, JavaArrayList.get, i)));

            return result;
        }

        static void handleDeviceAdded (JNIEnv* env, jclass, jobject obj)
        {
            if (obj == nullptr)
                return;

            StaticDeviceInfo info;
            info.name            = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.name));
            info.manufacturer    = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.manufacturer));
            info.serialNumber    = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.serialNumber));
            info.product         = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.product));

            info.id              = env->GetIntField (obj, JuceMidiDeviceInfo.id);
            info.transport       = env->GetIntField (obj, JuceMidiDeviceInfo.transport);
            info.type            = env->GetIntField (obj, JuceMidiDeviceInfo.type);

            info.src = arrayListStringToVector (env, env->GetObjectField (obj, JuceMidiDeviceInfo.src));
            info.dst = arrayListStringToVector (env, env->GetObjectField (obj, JuceMidiDeviceInfo.dst));

            if (auto strong = getSingleton())
                strong->listener.deviceAdded (info);
        }

        static void handleDeviceRemoved (JNIEnv*, jclass, jint id)
        {
            if (auto strong = getSingleton())
                strong->listener.deviceRemoved (id);
        }

        DeviceListener listener;
        GlobalRef deviceManager;

       #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
        CALLBACK (handleDeviceAdded, "handleDeviceAdded", "(Lcom/rmsl/juce/JuceMidiSupport$JuceMidiDeviceInfo;)V") \
        CALLBACK (handleDeviceRemoved, "handleDeviceRemoved", "(I)V" ) \
        STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \
        STATICMETHOD (getAndroidBluetoothManager,  "getAndroidBluetoothManager",  "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") \

        DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 24, javaMidiByteCode)
       #undef JNI_CLASS_MEMBERS
    };

    /*  A source of MIDI messages.
        This converts bytestream to UMP if necessary, but performs no other conversions.
    */
    class MidiSource
    {
    public:
        MidiSource (ump::Consumer& c, uint8_t portIndex, bool isUmpIn)
            : consumer (c),
              bytestreamDispatcher { portIndex, ump::PacketProtocol::MIDI_1_0, 4096 },
              isUmp (isUmpIn)
        {
        }

    private:
        void handleInput (JNIEnv* env, jbyteArray byteArray, jint offset, jint len, jlong timestamp)
        {
            jassert (byteArray != nullptr);
            thread_local std::vector<std::byte> buffer (4096);

            {
                auto* data = env->GetByteArrayElements (byteArray, nullptr);
                auto* bytes = reinterpret_cast<const std::byte*> (data + offset);

                buffer.resize ((size_t) len);
                std::copy (bytes, bytes + len, buffer.begin());

                env->ReleaseByteArrayElements (byteArray, data, 0);
            }

            const auto time = convertNativeTimestamp (timestamp);

            const auto send = [this] (const ump::View& view, double t)
            {
                const ump::Iterator b { view.data(), view.size() };
                consumer.consume (b, std::next (b), t);
            };

            if (isUmp)
            {
                swapByteOrderOfWordsIfNecessary (buffer);

                const auto words = reinterpret_cast<const uint32_t*> (buffer.data());
                const auto numWords = buffer.size() / sizeof (uint32_t);

                umpDispatcher.dispatch ({ words, numWords }, time, send);
            }
            else
            {
                bytestreamDispatcher.dispatch (buffer, time, send);
            }
        }

        /*  Native timestamps are based on System.nanoTime
            JUCE timestamps use Time::getMillisecondCounter
        */
        double convertNativeTimestamp (jlong absoluteNanos) const
        {
            const auto millis = std::invoke ([&]
            {
                // A timestamp of 0 means ASAP
                if (absoluteNanos == 0)
                    return (double) Time::getMillisecondCounter();

                const auto elapsedNanos = absoluteNanos - startTimeNative;
                const auto elapsedMillis = elapsedNanos / (jlong) 1e6;
                return (double) (startTimeMillis + elapsedMillis);
            });

            return millis * 0.001;
        }

        static void handleReceive (JNIEnv* env,
                                   MidiSource& myself,
                                   jbyteArray byteArray,
                                   jint offset,
                                   jint len,
                                   jlong timestamp)
        {
            myself.handleInput (env, byteArray, offset, len, timestamp);
        }

        ump::Consumer& consumer;
        ump::Dispatcher umpDispatcher;
        ump::BytestreamToUMPDispatcher bytestreamDispatcher;

        const jlong startTimeNative = midiClockMillis (getEnv());
        const uint32 startTimeMillis = Time::getMillisecondCounter();

        bool isUmp;

       #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
        CALLBACK (generatedCallback<&MidiSource::handleReceive>, "handleReceive", "(J[BIIJ)V")

        DECLARE_JNI_CLASS (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$NativeMidiReceiver")
       #undef JNI_CLASS_MEMBERS
    };

    /*  A destination for MIDI messages.
        This converts UMP to bytestream if necessary, but performs no other conversions.
        If the destination is using UMP transport, this won't convert to the target's preferred protocol.
        The connection is exclusive, so this should be shared between all entities that want to
        concurrently send MIDI to this destination.
    */
    class MidiDestination
    {
    public:
        static std::unique_ptr<MidiDestination> make (MidiReceiver outputReceiver, bool isUmp)
        {
            if (outputReceiver == MidiReceiver{})
                return {};

            auto result = rawToUniquePtr (new MidiDestination);
            result->outputReceiver = std::move (outputReceiver);
            result->isUMP = isUmp;
            return result;
        }

        bool send (ump::View v)
        {
            ump::Iterator b { v.data(), v.size() };
            return send (b, std::next (b));
        }

        bool send (ump::Iterator b, ump::Iterator e)
        {
            if (outputReceiver == MidiReceiver{})
                return false;

            if (isUMP)
            {
                auto* env = getEnv();
                const auto messageSize = size_t (e->data() - b->data()) * sizeof (uint32_t);

                LocalRef<jbyteArray> messageContent (env->NewByteArray ((jsize) messageSize));

                auto* rawBytes = env->GetByteArrayElements (messageContent.get(), nullptr);
                std::memcpy (rawBytes, b->data(), static_cast<size_t> (messageSize));

                swapByteOrderOfWordsIfNecessary (Span (unalignedPointerCast<std::byte*> (rawBytes), messageSize));

                env->ReleaseByteArrayElements (messageContent.get(), rawBytes, 0);

                outputReceiver.send (messageContent.get(), (jint) messageSize);
            }
            else
            {
                for (auto v : makeRange (b, e))
                {
                    toBytestream.convert (v, 0.0, [&] (ump::BytesOnGroup bytesView, double)
                    {
                        auto *env = getEnv();
                        const auto messageSize = (jsize) bytesView.bytes.size();

                        LocalRef<jbyteArray> messageContent (env->NewByteArray (messageSize));

                        auto *rawBytes = env->GetByteArrayElements (messageContent.get(), nullptr);
                        std::memcpy (rawBytes, bytesView.bytes.data(), static_cast<size_t> (messageSize));
                        env->ReleaseByteArrayElements (messageContent.get(), rawBytes, 0);

                        outputReceiver.send (messageContent.get(), messageSize);
                    });
                }
            }

            return true;
        }

    private:
        ump::ToBytestreamConverter toBytestream { 4096 };
        MidiReceiver outputReceiver;
        bool isUMP;
    };

    class ConverterWithOptionalProtocol
    {
    public:
        void setProtocol (std::optional<ump::PacketProtocol> newProtocol)
        {
            const auto previousProtocol = std::invoke ([&]
            {
                const SpinLock::ScopedLockType lock { mutex };
                return converter.has_value() ? std::make_optional (converter->getProtocol()) : std::nullopt;
            });

            if (newProtocol == previousProtocol)
                return;

            const SpinLock::ScopedLockType lock { mutex };

            if (newProtocol.has_value())
                converter.emplace (*newProtocol);
            else
                converter.reset();
        }

        template <typename Callback>
        bool convert (ump::Iterator b, ump::Iterator e, Callback&& callback)
        {
            SpinLock::ScopedTryLockType lock { mutex };

            if (! lock.isLocked())
            {
                // There's contention on the output lock!
                jassertfalse;
                return false;
            }

            if (! converter.has_value())
            {
                bool allSent = true;

                // filter out channel voice messages
                for (const auto& v : makeRange (b, e))
                {
                    const auto kind = ump::Utils::getMessageType (v[0]);

                    if (kind == ump::Utils::MessageKind::channelVoice1 || kind == ump::Utils::MessageKind::channelVoice2)
                    {
                        // Dropping this packet because we don't know whether the endpoint is prepared to receive it
                        allSent = false;
                        continue;
                    }

                    const ump::Iterator it { v.data(), v.size() };
                    allSent &= callback (it, std::next (it));
                }

                return allSent;
            }

            bool allSent = true;

            converter->convert (b, e, [&] (ump::View v)
            {
                const ump::Iterator it { v.data(), v.size() };
                allSent &= callback (it, std::next (it));
            });

            return allSent;
        }

    private:
        SpinLock mutex;
        std::optional<ump::GenericUMPConverter> converter;
    };

    struct Connection
    {
        virtual ~Connection() = default;

        virtual ump::EndpointId getEndpointId() const = 0;
        virtual std::optional<ump::Endpoint> getEndpoint() const = 0;
        virtual ump::StaticDeviceInfo getStaticDeviceInfo() const = 0;
        virtual bool send (ump::Iterator, ump::Iterator) = 0;

        virtual void addConsumer (ump::Consumer&) = 0;
        virtual void removeConsumer (ump::Consumer&) = 0;

        virtual void disconnected() = 0;
        virtual void addDisconnectionListener (ump::DisconnectionListener&) = 0;
        virtual void removeDisconnectionListener (ump::DisconnectionListener&) = 0;
    };

    class VirtualPortConnection : public Connection,
                                  private ump::Consumer
    {
    public:
        static std::unique_ptr<VirtualPortConnection> make (MidiReceiver outputReceiver, bool isUmp)
        {
            auto result = rawToUniquePtr (new VirtualPortConnection);

            result->src = std::make_unique<MidiSource> (static_cast<ump::Consumer&> (*result), 0, isUmp);
            result->dst = MidiDestination::make (std::move (outputReceiver), isUmp);
            result->isUmp = isUmp;

            if (result->src == nullptr && result->dst == nullptr)
                return {};

            return result;
        }

        ump::EndpointId getEndpointId() const override
        {
            const String v = "VIRTUAL " + String (isUmp ? "UMP" : "BYTESTREAM");
            return ump::EndpointId::makeSrcDst (v, v);
        }

        std::optional<ump::Endpoint> getEndpoint() const override
        {
            return endpoint;
        }

        ump::StaticDeviceInfo getStaticDeviceInfo() const override
        {
            return ump::StaticDeviceInfo{}.withTransport (isUmp ? ump::Transport::ump : ump::Transport::bytestream)
                                          .withHasSource (true)
                                          .withHasDestination (true);
        }

        void setEndpoint (std::optional<ump::Endpoint> ep)
        {
            endpoint = std::move (ep);
            converter.setProtocol (endpoint.has_value() ? endpoint->getProtocol() : std::nullopt);
        }

        jlong getHost() const
        {
            if (src != nullptr)
                return (jlong) src.get();

            return 0;
        }

        void addConsumer (ump::Consumer& x) override
        {
            consumers.add (x);
        }

        void removeConsumer (ump::Consumer& x) override
        {
            consumers.remove (x);
        }

        void disconnected() override
        {
            disconnectListeners.call ([] (auto& x) { x.disconnected(); });
        }

        void addDisconnectionListener (ump::DisconnectionListener& x) override
        {
            disconnectListeners.add (&x);
        }

        void removeDisconnectionListener (ump::DisconnectionListener& x) override
        {
            disconnectListeners.remove (&x);
        }

        bool send (ump::Iterator b, ump::Iterator e) override
        {
            if (dst != nullptr)
                return converter.convert (b, e, [&] (auto&&... args) { return dst->send (args...); });

            return false;
        }

    private:
        void consume (ump::Iterator b, ump::Iterator e, double time) override
        {
            consumers.call ([&] (auto& x) { x.consume (b, e, time); });
        }

        std::optional<ump::Endpoint> endpoint;
        WaitFreeListeners<Consumer> consumers;
        ConverterWithOptionalProtocol converter;
        ListenerList<ump::DisconnectionListener> disconnectListeners;
        std::unique_ptr<MidiSource> src;
        std::unique_ptr<MidiDestination> dst;
        bool isUmp = false;
    };

    class VirtualEndpointImplNative : public ump::DisconnectionListener,
                                      private ump::Consumer
    {
    public:
        static std::unique_ptr<VirtualEndpointImplNative> make (const String& name,
                                                                const ump::DeviceInfo& info,
                                                                const String& instanceId,
                                                                ump::PacketProtocol protocol,
                                                                Span<const ump::Block> blocks,
                                                                ump::BlocksAreStatic blocksAreStatic,
                                                                std::shared_ptr<VirtualPortConnection> connection)
        {
            if (connection == nullptr)
                return {};

            return rawToUniquePtr (new VirtualEndpointImplNative (name,
                                                                  info,
                                                                  instanceId,
                                                                  protocol,
                                                                  blocks,
                                                                  blocksAreStatic,
                                                                  connection));
        }

        ~VirtualEndpointImplNative() override
        {
            if (port != nullptr)
            {
                port->setEndpoint ({});
                port->removeConsumer (*this);
            }
        }

        ump::EndpointId getId() const
        {
            return port->getEndpointId();
        }

        bool setBlock (uint8_t index, const ump::Block& b)
        {
            if (port == nullptr)
                return false;

            const auto ep = port->getEndpoint();

            if (! ep.has_value())
                return false;

            auto endpoint = *ep;

            if (index >= endpoint.getBlocks().size())
                return false;

            const auto old = std::exchange (endpoint.getBlocks()[index], b);

            if (! b.nameMatches (old))
                sendFunctionBlockNameNotification (index);

            if (! b.infoMatches (old))
                sendFunctionBlockInfoNotification (index);

            port->setEndpoint (std::move (endpoint));

            return true;
        }

        bool setName (const String& name)
        {
            if (port == nullptr)
                return false;

            // Per the spec, there's a max length of 98 bytes for endpoint names
            if (name.getNumBytesAsUTF8() > 98)
                return false;

            const auto ep = port->getEndpoint();

            if (! ep.has_value())
                return false;

            auto endpoint = *ep;

            if (name == endpoint.getName())
                return true;

            port->setEndpoint (endpoint.withName (name));
            sendEndpointNameNotification();

            return true;
        }

        void disconnected() override
        {
            port = nullptr;
        }

    private:
        VirtualEndpointImplNative (const String& name,
                                   const ump::DeviceInfo& info,
                                   const String& instanceId,
                                   ump::PacketProtocol protocol,
                                   Span<const ump::Block> blocks,
                                   ump::BlocksAreStatic blocksAreStatic,
                                   std::shared_ptr<VirtualPortConnection> c)
            : port (std::move (c))
        {
            port->addConsumer (*this);
            port->setEndpoint (makeEndpoint (name, info, instanceId, protocol, blocks, blocksAreStatic));

            processEndpointDiscovery (ump::View { ump::Factory::makeEndpointDiscovery (1, 1, std::byte { 0x1f }).data() });
            processFunctionBlockDiscovery (ump::View { ump::Factory::makeFunctionBlockDiscovery (0xff, std::byte { 0x03 }).data() });
        }

        void consume (ump::Iterator b, ump::Iterator e, double) override
        {
            for (auto v : makeRange (b, e))
                processPacket (v);
        }

        void processPacket (ump::View v)
        {
            const auto kind = ump::Utils::getMessageType (v[0]);

            if (kind != ump::Utils::MessageKind::stream)
                return;

            const auto status = ump::Utils::U8<1>::get (v[0]);

            switch (status)
            {
                case 0x0:
                    processEndpointDiscovery (v);
                    break;

                case 0x5:
                    processStreamConfigRequest (v);
                    break;

                case 0x10:
                    processFunctionBlockDiscovery (v);
                    break;
            }
        }

        void processEndpointDiscovery (ump::View v)
        {
            const auto filterBitmap = ump::Utils::U8<3>::get (v[1]);

            if ((filterBitmap & 0x01) != 0)
                sendEndpointInfoNotification();

            if ((filterBitmap & 0x02) != 0)
                sendDeviceIdentityNotification();

            if ((filterBitmap & 0x04) != 0)
                sendEndpointNameNotification();

            if ((filterBitmap & 0x08) != 0)
                sendProductInstanceIdNotification();

            if ((filterBitmap & 0x10) != 0)
                sendStreamConfigurationNotification();
        }

        void processStreamConfigRequest (ump::View)
        {
            // Currently we don't support changing stream configuration, so just reply with
            // the current configuration.
            sendStreamConfigurationNotification();
        }

        void processFunctionBlockDiscovery (ump::View v)
        {
            const auto blockNumber = ump::Utils::U8<2>::get (v[0]);
            const auto filterBitmap = ump::Utils::U8<3>::get (v[0]);

            const auto sendFunctionBlockInfo = [this, filterBitmap] (auto index)
            {
                if ((filterBitmap & 0x01) != 0)
                    sendFunctionBlockInfoNotification (index);

                if ((filterBitmap & 0x02) != 0)
                    sendFunctionBlockNameNotification (index);
            };

            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            if (blockNumber < endpoint->getBlocks().size())
            {
                sendFunctionBlockInfo (blockNumber);
            }
            else if (blockNumber == 0xff)
            {
                for (uint8_t i = 0; i < endpoint->getBlocks().size(); ++i)
                    sendFunctionBlockInfo (i);
            }
        }

        void sendFunctionBlockNameNotification (uint8_t index)
        {
            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            if (index >= endpoint->getBlocks().size())
            {
                jassertfalse;
                return;
            }

            ump::Factory::makeFunctionBlockNameNotification (index, endpoint->getBlocks()[index].getName(), [this] (auto v)
            {
                const ump::Iterator iterator { v.data(), v.size() };
                port->send (iterator, std::next (iterator));
            });
        }

        void sendFunctionBlockInfoNotification (uint8_t index)
        {
            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            if (index >= endpoint->getBlocks().size())
            {
                jassertfalse;
                return;
            }

            const auto packet = ump::Factory::makeFunctionBlockInfoNotification (index, endpoint->getBlocks()[index].getInfo());
            const ump::Iterator iterator { packet.data(), packet.size() };
            port->send (iterator, std::next (iterator));
        }

        void sendEndpointNameNotification()
        {
            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            ump::Factory::makeEndpointNameNotification (endpoint->getName(), [this] (auto v)
            {
                const ump::Iterator iterator { v.data(), v.size() };
                port->send (iterator, std::next (iterator));
            });
        }

        void sendDeviceIdentityNotification()
        {
            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            const auto packet = ump::Factory::makeDeviceIdentityNotification (endpoint->getDeviceInfo());
            const ump::Iterator iterator { packet.data(), packet.size() };
            port->send (iterator, std::next (iterator));
        }

        void sendEndpointInfoNotification()
        {
            const auto ep = port->getEndpoint();

            if (! ep.has_value())
                return;

            auto& endpoint = *ep;

            const auto info = ump::EndpointInfo{}.withMidi1Support (endpoint.hasMidi1Support())
                                                 .withMidi2Support (endpoint.hasMidi2Support())
                                                 .withNumFunctionBlocks ((uint8_t) endpoint.getBlocks().size())
                                                 .withReceiveJRSupport (endpoint.hasReceiveJRSupport())
                                                 .withTransmitJRSupport (endpoint.hasTransmitJRSupport())
                                                 .withStaticFunctionBlocks (endpoint.hasStaticBlocks())
                                                 .withVersion (endpoint.getUMPVersionMajor(), endpoint.getUMPVersionMinor());
            const auto packet = ump::Factory::makeEndpointInfoNotification (info);
            const ump::Iterator iterator { packet.data(), packet.size() };
            port->send (iterator, std::next (iterator));
        }

        void sendProductInstanceIdNotification()
        {
            const auto endpoint = port->getEndpoint();

            if (! endpoint.has_value())
                return;

            ump::Factory::makeProductInstanceIdNotification (endpoint->getProductInstanceId(), [this] (auto v)
            {
                const ump::Iterator iterator { v.data(), v.size() };
                port->send (iterator, std::next (iterator));
            });
        }

        void sendStreamConfigurationNotification()
        {
            const auto ep = port->getEndpoint();

            if (! ep.has_value())
                return;

            auto& endpoint = *ep;
            const auto protocol = endpoint.getProtocol();

            if (! protocol.has_value())
            {
                jassertfalse;
                return;
            }

            const auto info = ump::StreamConfiguration{}.withProtocol (*protocol)
                                                        .withReceiveTimestamp (endpoint.isReceiveJREnabled())
                                                        .withTransmitTimestamp (endpoint.isTransmitJREnabled());
            const auto packet = ump::Factory::makeStreamConfigurationNotification (info);
            const ump::Iterator iterator { packet.data(), packet.size() };
            port->send (iterator, std::next (iterator));
        }

        static ump::Endpoint makeEndpoint (const String& name,
                                           const ump::DeviceInfo& info,
                                           const String& instanceId,
                                           ump::PacketProtocol protocol,
                                           Span<const ump::Block> blocks,
                                           ump::BlocksAreStatic blocksAreStatic)
        {
            return ump::Endpoint{}.withName (name)
                                  .withDeviceInfo (info)
                                  .withProductInstanceId (instanceId)
                                  .withProtocol (protocol)
                                  .withMidi1Support (protocol == ump::PacketProtocol::MIDI_1_0)
                                  .withMidi2Support (protocol == ump::PacketProtocol::MIDI_2_0)
                                  .withUMPVersion (1, 1)
                                  .withBlocks (blocks)
                                  .withStaticBlocks (blocksAreStatic == ump::BlocksAreStatic::yes);
        }

        std::shared_ptr<VirtualPortConnection> port;
    };

    class LegacyVirtualPortImplNative : public ump::DisconnectionListener
    {
    public:
        static std::unique_ptr<LegacyVirtualPortImplNative> make (std::shared_ptr<VirtualPortConnection> x)
        {
            if (x == nullptr)
                return {};

            return rawToUniquePtr (new LegacyVirtualPortImplNative { x });
        }

        ump::EndpointId getId() const
        {
            return port->getEndpointId();
        }

        void disconnected() override
        {
            port = nullptr;
        }

    private:
        explicit LegacyVirtualPortImplNative (std::shared_ptr<VirtualPortConnection> x)
            : port (std::move (x)) {}

        std::shared_ptr<VirtualPortConnection> port;
    };

    struct VirtualMidiPortManagerListener
    {
        virtual ~VirtualMidiPortManagerListener() = default;
        virtual void virtualConnectionAdded() = 0;
        virtual void virtualConnectionRemoved (std::shared_ptr<Connection>) = 0;
    };

    class VirtualMidiPortManager
    {
    public:
        static VirtualMidiPortManager& getSingleton()
        {
            static VirtualMidiPortManager result;
            return result;
        }

        std::shared_ptr<VirtualEndpointImplNative> createNativeVirtualEndpoint (const String& name,
                                                                                const ump::DeviceInfo& info,
                                                                                const String& productInstance,
                                                                                ump::PacketProtocol protocol,
                                                                                Span<const ump::Block> blocks,
                                                                                ump::BlocksAreStatic blocksAreStatic)
        {
            if (! isVirtualUmpAvailable())
                return {};

            if (umpPort == nullptr)
            {
                // The virtual port is not available, maybe the service hasn't started yet
                return {};
            }

            if (umpConnection.lock() != nullptr)
            {
                // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times
                jassertfalse;
                return {};
            }

            std::shared_ptr result = VirtualEndpointImplNative::make (name,
                                                                      info,
                                                                      productInstance,
                                                                      protocol,
                                                                      blocks,
                                                                      blocksAreStatic,
                                                                      umpPort);

            umpConnection = result;
            return result;
        }

        std::shared_ptr<LegacyVirtualPortImplNative> createLegacyVirtualInput()
        {
            if (! isVirtualBytestreamAvailable())
                return {};

            if (bytestreamPort == nullptr)
            {
                // The virtual port is not available, maybe the service hasn't started yet
                return {};
            }

            if (srcConnection.lock() != nullptr)
            {
                // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times
                jassertfalse;
                return {};
            }

            std::shared_ptr result = LegacyVirtualPortImplNative::make (bytestreamPort);
            srcConnection = result;
            return result;
        }

        std::shared_ptr<LegacyVirtualPortImplNative> createLegacyVirtualOutput()
        {
            if (! isVirtualBytestreamAvailable())
                return {};

            if (bytestreamPort == nullptr)
            {
                // The virtual port is not available, maybe the service hasn't started yet
                return {};
            }

            if (dstConnection.lock() != nullptr)
            {
                // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times
                jassertfalse;
                return {};
            }

            std::shared_ptr result = LegacyVirtualPortImplNative::make (bytestreamPort);
            dstConnection = result;
            return result;
        }

        void addListener (VirtualMidiPortManagerListener& x)
        {
            listeners.add (&x);
        }

        void removeListener (VirtualMidiPortManagerListener& x)
        {
            listeners.remove (&x);
        }

        bool isBytestreamPortAvailable() const
        {
            return bytestreamPort != nullptr;
        }

        bool isUmpPortAvailable() const
        {
            return umpPort != nullptr;
        }

        std::shared_ptr<VirtualPortConnection> getBytestreamConnection() const
        {
            return bytestreamPort;
        }

        std::shared_ptr<VirtualPortConnection> getUmpConnection() const
        {
            return umpPort;
        }

        static void setVirtualMidiBytestreamEnabled (bool x)
        {
            if (VirtualMidiServices != nullptr)
            {
                getEnv()->CallStaticVoidMethod (VirtualMidiServices,
                                                VirtualMidiServices.setVirtualMidiBytestreamEnabled,
                                                getAppContext().get(),
                                                (jboolean) x);
            }
        }

        static void setVirtualMidiUmpEnabled (bool x)
        {
            if (VirtualMidiServices != nullptr)
            {
                getEnv()->CallStaticVoidMethod (VirtualMidiServices,
                                                VirtualMidiServices.setVirtualMidiUmpEnabled,
                                                getAppContext().get(),
                                                (jboolean) x);
            }
        }

    private:
        VirtualMidiPortManager() = default;

        static bool isVirtualBytestreamAvailable()
        {
            if (VirtualMidiServices != nullptr)
            {
                return getEnv()->CallStaticBooleanMethod (VirtualMidiServices,
                                                          VirtualMidiServices.isVirtualBytestreamAvailable,
                                                          getAppContext().get());
            }

            return false;
        }

        static bool isVirtualUmpAvailable()
        {
            if (VirtualMidiServices != nullptr)
            {
                return getEnv()->CallStaticBooleanMethod (VirtualMidiServices,
                                                          VirtualMidiServices.isVirtualUmpAvailable,
                                                          getAppContext().get());
            }

            return false;
        }

        jobject addPort (JNIEnv* env, jobject newReceiver, jboolean isUmp)
        {
            std::shared_ptr port = VirtualPortConnection::make (MidiReceiver { GlobalRef { LocalRef { newReceiver } } }, isUmp);

            if (port == nullptr)
                return {};

            auto* result = env->NewObject (NativeMidiReceiver, NativeMidiReceiver.constructor, port->getHost());
            MidiReceiver key { GlobalRef { LocalRef { env->NewLocalRef (result) } } };
            (isUmp ? umpPort : bytestreamPort) = port;
            (isUmp ? umpReceiver : bytestreamReceiver) = key;

            if (! isUmp)
            {
                StaticDeviceInfo info;
                info.dst = { "" };
                info.src = { "" };
                info.transport = TRANSPORT_BYTESTREAM;
                port->setEndpoint (info.getEquivalentBytestreamEndpoint());
            }

            listeners.call ([&] (auto& x) { x.virtualConnectionAdded(); });
            return result;
        }

        void removePort (JNIEnv* env, jobject previouslyAdded)
        {
            MidiReceiver key { GlobalRef { LocalRef { env->NewLocalRef (previouslyAdded) } } };

            const auto connection = std::invoke ([&]() -> std::shared_ptr<Connection>
            {
                if (key == umpReceiver)
                    return umpPort;

                if (key == bytestreamReceiver)
                    return bytestreamPort;

                return {};
            });

            if (connection == nullptr)
            {
                jassertfalse;
                return;
            }

            listeners.call ([&] (auto& x) { x.virtualConnectionRemoved (connection); });

            if (key == umpReceiver)
                if (auto strong = umpConnection.lock())
                    strong->disconnected();

            if (key == bytestreamReceiver)
            {
                if (auto strong = srcConnection.lock())
                    strong->disconnected();

                if (auto strong = dstConnection.lock())
                    strong->disconnected();
            }

            for (auto* ptr : { &bytestreamPort, &umpPort })
                if (*ptr == connection)
                    ptr->reset();
        }

        std::shared_ptr<VirtualPortConnection> bytestreamPort, umpPort;
        MidiReceiver bytestreamReceiver, umpReceiver;

        std::weak_ptr<ump::DisconnectionListener> umpConnection;
        std::weak_ptr<ump::DisconnectionListener> srcConnection;
        std::weak_ptr<ump::DisconnectionListener> dstConnection;
        ListenerList<VirtualMidiPortManagerListener> listeners;

        static jobject addVirtualPort (JNIEnv* env, jclass, jobject newReceiver, jboolean isUmp)
        {
            return getSingleton().addPort (env, newReceiver, isUmp);
        }

        static void removeVirtualPort (JNIEnv* env, jclass, jobject previouslyAdded)
        {
            getSingleton().removePort (env, previouslyAdded);
        }

       #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
        CALLBACK (addVirtualPort, "addVirtualPort", "(Landroid/media/midi/MidiReceiver;Z)Landroid/media/midi/MidiReceiver;") \
        CALLBACK (removeVirtualPort, "removeVirtualPort", "(Landroid/media/midi/MidiReceiver;)V") \
        STATICMETHOD (isVirtualBytestreamAvailable, "isVirtualBytestreamAvailable", "(Landroid/content/Context;)Z") \
        STATICMETHOD (isVirtualUmpAvailable, "isVirtualUmpAvailable", "(Landroid/content/Context;)Z") \
        STATICMETHOD (setVirtualMidiBytestreamEnabled, "setVirtualMidiBytestreamEnabled", "(Landroid/content/Context;Z)V") \
        STATICMETHOD (setVirtualMidiUmpEnabled, "setVirtualMidiUmpEnabled", "(Landroid/content/Context;Z)V") \

        DECLARE_OPTIONAL_JNI_CLASS_WITH_BYTECODE (VirtualMidiServices, "com/rmsl/juce/VirtualMidiServices", 24, nullptr, true)
       #undef JNI_CLASS_MEMBERS
    };

    /*  This type holds the bytes of a Universal MIDI Packet in 'string order' */
    struct StreamPacketBytes
    {
        std::array<std::byte, 16> data;
        size_t size = 0;

        static StreamPacketBytes asPlainBytes (ump::View v)
        {
            StreamPacketBytes result;

            for (const auto word : v)
            {
                result.data[result.size + 0] = std::byte { ump::Utils::U8<0>::get (word) };
                result.data[result.size + 1] = std::byte { ump::Utils::U8<1>::get (word) };
                result.data[result.size + 2] = std::byte { ump::Utils::U8<2>::get (word) };
                result.data[result.size + 3] = std::byte { ump::Utils::U8<3>::get (word) };
                result.size += 4;
            }

            return result;
        }
    };

    /*  Endpoint name, product instance ID, and function block name may all be spread across
        several packets, and this type can reconstitute a UTF-8 string from these packets.
    */
    class StringBuilder
    {
    public:
        StringBuilder()
        {
            buffer.reserve (7 * 16);
        }

        void add (ump::View v)
        {
            if (ump::Utils::getMessageType (v[0]) != ump::Utils::MessageKind::stream)
            {
                jassertfalse;
                return;
            }

            const auto status = ump::Utils::U8<1>::get (v[0]);

            if (status != 0x03 && status != 0x04 && status != 0x12)
            {
                jassertfalse;
                return;
            }

            const auto format = (ump::Utils::U8<0>::get (v[0]) >> 2) & 0x3;

            switch (format)
            {
                case 0:
                {
                    beginString();
                    append (v);
                    endString();
                    break;
                }

                case 1:
                {
                    beginString();
                    append (v);
                    break;
                }

                case 2:
                {
                    jassert (! buffer.empty());
                    append (v);
                    break;
                }

                case 3:
                {
                    jassert (! buffer.empty());
                    append (v);
                    endString();
                    break;
                }
            }
        }

        std::optional<String> get() const
        {
            if (buffer.empty() || buffer.back() == std::byte{})
                return String::fromUTF8 (reinterpret_cast<const char*> (buffer.data()), (int) buffer.size());

            return std::nullopt;
        }

    private:
        void beginString()
        {
            buffer.clear();
        }

        void append (ump::View v)
        {
            const auto status = ump::Utils::U8<1>::get (v[0]);
            const auto offset = status == 0x12 ? 3 : 2;
            const auto packetAsBytes = StreamPacketBytes::asPlainBytes (v);

            buffer.insert (buffer.end(), packetAsBytes.data.begin() + offset, packetAsBytes.data.end());
        }

        void endString()
        {
            buffer.push_back ({}); // add null terminator just in case
        }

        std::vector<std::byte> buffer;
    };

    struct StreamProcessorQueueDelegate
    {
        virtual ~StreamProcessorQueueDelegate() = default;
        virtual void processStreamPacket (ump::View) = 0;
    };

    /*  Filters out stream messages and dispatches them to the main thread,
        where they are passed to the delegate for further processing.
    */
    class StreamProcessorQueue final : private Timer
    {
    public:
        explicit StreamProcessorQueue (StreamProcessorQueueDelegate& d)
            : delegate (d)
        {
            startTimer (20);
        }

        ~StreamProcessorQueue() override
        {
            stopTimer();
        }

        /** To be called from the MIDI input thread */
        void consume (ump::Iterator b, ump::Iterator e)
        {
            for (auto v : makeRange (b, e))
            {
                if (ump::Utils::getMessageType (v[0]) != ump::Utils::MessageKind::stream)
                    continue;

                size_t i = 0;
                fifo.write ((int) v.size()).forEach ([&] (auto index)
                {
                    buffer[(size_t) index] = v[i++];
                });
            }
        }

    private:
        void timerCallback() override
        {
            const auto callback = [this] (ump::View v, double)
            {
                delegate.processStreamPacket (v);
            };

            const auto scope = fifo.read (fifo.getNumReady());
            dispatcher.dispatch ({ buffer.data() + scope.startIndex1, (size_t) scope.blockSize1 }, 0.0, callback);
            dispatcher.dispatch ({ buffer.data() + scope.startIndex2, (size_t) scope.blockSize2 }, 0.0, callback);
        }

        static constexpr auto queueSize = 4096;
        StreamProcessorQueueDelegate& delegate;
        std::vector<uint32_t> buffer = std::vector<uint32_t> (queueSize);
        AbstractFifo fifo { queueSize };
        ump::Dispatcher dispatcher;
    };

    struct EndpointInfoBuilderDelegate
    {
        virtual ~EndpointInfoBuilderDelegate() = default;
        virtual void sendFunctionBlockDiscovery() = 0;
    };

    /*  Parses incoming stream messages to build up a cache of all the information known about
        a connected endpoint.
        Waits until all function block information has been received before notifying all
        EndpointListeners, and then notifies listeners on each subsequent configuration change.
    */
    class EndpointInfoBuilder : public StreamProcessorQueueDelegate
    {
    public:
        EndpointInfoBuilder (const ump::EndpointId& idToUse, EndpointInfoBuilderDelegate& d)
            : id (idToUse), delegate (d)
        {
        }

        std::optional<ump::Endpoint> getEndpoint() const
        {
            return endpoint;
        }

        void processStreamPacket (ump::View v) override
        {
            JUCE_ASSERT_MESSAGE_THREAD

            const auto status = ump::Utils::U8<1>::get (v[0]);

            switch (status)
            {
                case 0x1:
                    processEndpointInfo (v);
                    break;

                case 0x2:
                    processDeviceIdentity (v);
                    break;

                case 0x3:
                    processEndpointName (v);
                    break;

                case 0x4:
                    processProductInstanceID (v);
                    break;

                case 0x6:
                    processStreamConfigNotification (v);
                    break;

                case 0x11:
                    processFunctionBlockInfo (v);
                    break;

                case 0x12:
                    processFunctionBlockName (v);
                    break;
            }

            sendNotificationIfValid();
        }

        void addListener (ump::EndpointsListener& l)
        {
            changeListeners.add (&l);
        }

        void removeListener (ump::EndpointsListener& l)
        {
            changeListeners.remove (&l);
        }

    private:
        void sendNotificationIfValid()
        {
            const auto numSpecifiedBlocks = endpoint.has_value() ? endpoint->getBlocks().size() : 0;
            const auto requiredBitmap = (uint32_t) (((uint64_t) 1 << numSpecifiedBlocks) - 1);

            if ((specifiedBlocks & requiredBitmap) != requiredBitmap)
                return;

            if ((specifiedBlockNames & requiredBitmap) != requiredBitmap)
                return;

            changeListeners.call ([] (auto& x) { x.endpointsChanged(); });
        }

        void processEndpointInfo (ump::View v)
        {
            const auto prevNumFunctionBlocks = endpoint.has_value() ? endpoint->getBlocks().size() : 0;

            endpoint = endpoint.value_or (ump::Endpoint{})
                               .withUMPVersion (ump::Utils::U8<2>::get (v[0]), ump::Utils::U8<3>::get (v[0]))
                               .withMidi1Support (ump::Utils::U8<2>::get (v[1]) & 0x1)
                               .withMidi2Support (ump::Utils::U8<2>::get (v[1]) & 0x2)
                               .withTransmitJRSupport (ump::Utils::U8<3>::get (v[1]) & 0x1)
                               .withReceiveJRSupport (ump::Utils::U8<3>::get (v[1]) & 0x2)
                               .withStaticBlocks (ump::Utils::U8<0>::get (v[1]) & 0x80)
                               .withNumBlocks (ump::Utils::U8<0>::get (v[1]) & 0x7f);

            if (endpoint->getBlocks().size() > prevNumFunctionBlocks)
                delegate.sendFunctionBlockDiscovery();
        }

        void processDeviceIdentity (ump::View v)
        {
            const auto bytes = StreamPacketBytes::asPlainBytes (v);
            ump::DeviceInfo info;
            std::copy (bytes.data.data() + 0x5, bytes.data.data() + 0x8, info.manufacturer.begin());
            std::copy (bytes.data.data() + 0x8, bytes.data.data() + 0xa, info.family.begin());
            std::copy (bytes.data.data() + 0xa, bytes.data.data() + 0xc, info.modelNumber.begin());
            std::copy (bytes.data.data() + 0xc, bytes.data.data() + 0x10, info.revision.begin());
            endpoint = endpoint.value_or (ump::Endpoint{}).withDeviceInfo (info);
        }

        void processEndpointName (ump::View v)
        {
            endpointNameBuilder.add (v);

            if (auto x = endpointNameBuilder.get())
                endpoint = endpoint.value_or (ump::Endpoint{}).withName (*x);
        }

        void processProductInstanceID (ump::View v)
        {
            productIdBuilder.add (v);

            if (auto x = productIdBuilder.get())
                endpoint = endpoint.value_or (ump::Endpoint{}).withProductInstanceId (*x);
        }

        void processStreamConfigNotification (ump::View v)
        {
            const auto packetProtocol = std::invoke ([&]() -> std::optional<ump::PacketProtocol>
            {
                switch (ump::Utils::U8<2>::get (v[0]))
                {
                    case 1:
                        return ump::PacketProtocol::MIDI_1_0;
                    case 2:
                        return ump::PacketProtocol::MIDI_2_0;
                }

                return {};
            });

            const auto flags = ump::Utils::U8<3>::get (v[0]);
            const auto rxjr = flags & 0x2;
            const auto txjr = flags & 0x1;

            endpoint = endpoint.value_or (ump::Endpoint{})
                               .withProtocol (packetProtocol)
                               .withReceiveJREnabled (rxjr)
                               .withTransmitJREnabled (txjr);
        }

        void processFunctionBlockInfo (ump::View v)
        {
            const auto index = (size_t) (ump::Utils::U8<2>::get (v[0]) & 0x7f);

            if (! endpoint.has_value() || index >= endpoint->getBlocks().size())
            {
                // Got a block notification for a block that doesn't exist!
                jassertfalse;
                return;
            }

            endpoint->getBlocks()[index] = ump::Block{}.withEnabled (ump::Utils::U8<2>::get (v[0]) & 0x80)
                                                       .withUiHint ((ump::BlockUiHint) ((ump::Utils::U8<3>::get (v[0]) >> 4) & 0x3))
                                                       .withMIDI1ProxyKind ((ump::BlockMIDI1ProxyKind) ((ump::Utils::U8<3>::get (v[0]) >> 2) & 0x3))
                                                       .withDirection ((ump::BlockDirection) ((ump::Utils::U8<3>::get (v[0]) >> 0) & 0x3))
                                                       .withFirstGroup (ump::Utils::U8<0>::get (v[1]))
                                                       .withNumGroups (ump::Utils::U8<1>::get (v[1]))
                                                       .withMaxSysex8Streams (ump::Utils::U8<3>::get (v[1]));

            specifiedBlocks |= (1 << index);
        }

        void processFunctionBlockName (ump::View v)
        {
            const auto blockIndex = ump::Utils::U8<2>::get (v[0]);

            if (blockIndex >= functionBlockNameBuilders.size())
            {
                // block with this index cannot exist
                jassertfalse;
                return;
            }

            if (! endpoint.has_value() || blockIndex >= endpoint->getBlocks().size())
            {
                // setting a name for a block that doesn't exist on this endpoint
                jassertfalse;
                return;
            }

            auto& builder = functionBlockNameBuilders[blockIndex];

            builder.add (v);

            if (auto x = builder.get())
            {
                auto& block = endpoint->getBlocks()[blockIndex];
                block = block.withName (*x);

                specifiedBlockNames |= (1 << blockIndex);
            }
        }

        ump::EndpointId id;
        EndpointInfoBuilderDelegate& delegate;

        ListenerList<ump::EndpointsListener> changeListeners;

        std::optional<ump::Endpoint> endpoint;

        std::array<StringBuilder, 32> functionBlockNameBuilders;
        StringBuilder endpointNameBuilder, productIdBuilder;

        // Toggle each bit on to indicate that we've received information about the block at that index
        uint32_t specifiedBlocks = 0;
        uint32_t specifiedBlockNames = 0;
    };

    /*  A connection to a UMP device, which is assumed to always have a single input and output port. */
    class UmpConnection : public Connection,
                          private ump::EndpointsListener,
                          private EndpointInfoBuilderDelegate,
                          private ump::Consumer
    {
    public:
        ~UmpConnection() override
        {
            for (auto& port : ports)
                port.stop();
        }

        static std::shared_ptr<UmpConnection> make (const DeviceManager& manager,
                                                    const StaticDeviceInfo& info,
                                                    const ump::EndpointId& e)
        {
            auto result = rawToUniquePtr (new UmpConnection { e });

            result->src = std::make_unique<MidiSource> (static_cast<ump::Consumer&> (*result), 0, true);

            if (result->src != nullptr)
            {
                if (auto port = manager.openMidiInputPortWithID (info.id, (jint) 0, (jlong) result->src.get()))
                {
                    port.start();
                    result->ports.push_back (std::move (port));
                }
            }

            if (auto port = manager.openMidiOutputPortWithID (info.id, (jint) 0))
            {
                result->dst = MidiDestination::make (MidiReceiver { port.get() }, true);
                result->ports.push_back (std::move (port));
            }

            if (result->src == nullptr && result->dst == nullptr)
            {
                // Unable to open a connection in either direction to this device!
                jassertfalse;
                return {};
            }

            result->staticInfo = info;
            result->sendEndpointDiscovery();
            return result;
        }

        ump::EndpointId getEndpointId() const override
        {
            return endpointId;
        }

        std::optional<ump::Endpoint> getEndpoint() const override
        {
            return infoBuilder.getEndpoint();
        }

        ump::StaticDeviceInfo getStaticDeviceInfo() const override
        {
            return staticInfo.getUmpStaticDeviceInfo();
        }

        bool send (ump::Iterator b, ump::Iterator e) override
        {
            if (dst == nullptr)
                return false;

            return converter.convert (b, e, [&] (auto&&... args) { return dst->send (args...); });
        }

        void addConsumer (ump::Consumer& c) override
        {
            consumers.add (c);
        }

        void removeConsumer (ump::Consumer& c) override
        {
            consumers.remove (c);
        }

        void disconnected() override
        {
            disconnectListeners.call ([] (auto& x) { x.disconnected(); });
        }

        void addDisconnectionListener (ump::DisconnectionListener& l) override
        {
            disconnectListeners.add (&l);
        }

        void removeDisconnectionListener (ump::DisconnectionListener& l) override
        {
            disconnectListeners.remove (&l);
        }

        void addEndpointListener (ump::EndpointsListener& l)
        {
            infoBuilder.addListener (l);
        }

        void removeEndpointListener (ump::EndpointsListener& l)
        {
            infoBuilder.removeListener (l);
        }

    private:
        explicit UmpConnection (const ump::EndpointId& idToUse)
            : endpointId (idToUse)
        {
            infoBuilder.addListener (*this);
        }

        /*  Called when 'our' endpoint is changed. */
        void endpointsChanged() override
        {
            const auto newProtocol = std::invoke ([&]() -> std::optional<ump::PacketProtocol>
            {
                if (const auto& e = infoBuilder.getEndpoint())
                    return e->getProtocol();

                return std::nullopt;
            });

            converter.setProtocol (newProtocol);
        }

        void sendEndpointDiscovery()
        {
            // The 'filter bitmap' requests all fields that are defined in the current UMP spec
            if (dst != nullptr)
                dst->send (ump::View { ump::Factory::makeEndpointDiscovery (1, 1, std::byte { 0x1f }).data() });
        }

        void sendFunctionBlockDiscovery() override
        {
            if (dst != nullptr)
                dst->send (ump::View { ump::Factory::makeFunctionBlockDiscovery (0xff, std::byte { 0x3 }).data() });
        }

        void consume (ump::Iterator b, ump::Iterator e, double t) override
        {
            // Forward to all interested listeners
            consumers.call ([&] (auto& x) { x.consume (b, e, t); });

            // Filter out stream messages for processing on the main thread
            queue.consume (b, e);
        }

        ListenerList<ump::DisconnectionListener> disconnectListeners;
        WaitFreeListeners<ump::Consumer> consumers;

        StaticDeviceInfo staticInfo;
        ump::EndpointId endpointId;
        EndpointInfoBuilder infoBuilder { endpointId, *this };
        StreamProcessorQueue queue { infoBuilder };

        std::unique_ptr<MidiSource> src;
        std::unique_ptr<MidiDestination> dst;
        std::vector<MidiPort> ports;

        ConverterWithOptionalProtocol converter;
    };

    class BytestreamConnection : public Connection,
                                 private ump::Consumer
    {
    public:
        ~BytestreamConnection() override
        {
            for (auto& port : ports)
                port.stop();
        }

        static std::shared_ptr<BytestreamConnection> make (const DeviceManager& manager,
                                                           const StaticDeviceInfo& info,
                                                           const ump::EndpointId& e)
        {
            auto result = rawToUniquePtr (new BytestreamConnection);

            for (size_t i = 0; i < info.src.size(); ++i)
            {
                auto item = std::make_unique<MidiSource> (static_cast<ump::Consumer&> (*result), (int) i, false);
                auto port = manager.openMidiInputPortWithID (info.id, (jint) i, (jlong) item.get());

                if (port == nullptr)
                    continue;

                result->src.push_back (std::move (item));
                port.start();
                result->ports.push_back (std::move (port));
            }

            for (size_t i = 0; i < info.dst.size(); ++i)
            {
                if (auto port = manager.openMidiOutputPortWithID (info.id, (int) i))
                {
                    if (auto dst = MidiDestination::make (MidiReceiver { port.get() }, false))
                    {
                        result->dst.push_back (std::move (dst));
                        result->ports.push_back (std::move (port));
                    }
                }
            }

            result->endpointId = e;
            result->endpoint = info.getEquivalentBytestreamEndpoint();
            result->staticInfo = info;

            return result;
        }

        ump::EndpointId getEndpointId() const override
        {
            return endpointId;
        }

        std::optional<ump::Endpoint> getEndpoint() const override
        {
            return endpoint;
        }

        ump::StaticDeviceInfo getStaticDeviceInfo() const override
        {
            return staticInfo.getUmpStaticDeviceInfo();
        }

        bool send (ump::Iterator b, ump::Iterator e) override
        {
            bool success = true;

            for (const auto v : makeRange (b, e))
            {
                const auto kind = ump::Utils::getMessageType (v[0]);

                if (ump::Utils::isGroupless (kind))
                {
                    success = false;
                    continue;
                }

                const auto groupIndex = ump::Utils::getGroup (v[0]);

                if (groupIndex >= dst.size())
                {
                    success = false;
                    continue;
                }

                if (auto& x = dst[(size_t) groupIndex])
                    x->send (v);
            }

            return success;
        }

        void addConsumer (ump::Consumer& c) override
        {
            consumers.add (c);
        }

        void removeConsumer (ump::Consumer& c) override
        {
            consumers.remove (c);
        }

        void disconnected() override
        {
            disconnectListeners.call ([] (auto& x) { x.disconnected(); });
        }

        void addDisconnectionListener (ump::DisconnectionListener& l) override
        {
            disconnectListeners.add (&l);
        }

        void removeDisconnectionListener (ump::DisconnectionListener& l) override
        {
            disconnectListeners.remove (&l);
        }

    private:
        void consume (ump::Iterator b, ump::Iterator e, double t) override
        {
            consumers.call ([&] (auto& x) { x.consume (b, e, t); });
        }

        ListenerList<ump::DisconnectionListener> disconnectListeners;
        WaitFreeListeners<ump::Consumer> consumers;

        StaticDeviceInfo staticInfo;
        ump::EndpointId endpointId;
        std::optional<ump::Endpoint> endpoint;

        std::vector<std::unique_ptr<MidiSource>> src;
        std::vector<std::unique_ptr<MidiDestination>> dst;
        std::vector<MidiPort> ports;
    };

    class Client : private DeviceManagerListener,
                   private VirtualMidiPortManagerListener
    {
    public:
        void getEndpoints (std::vector<ump::EndpointId>& buffer) const
        {
            std::transform (infoForEndpoint.begin(),
                            infoForEndpoint.end(),
                            std::back_inserter (buffer),
                            [] (auto& pair) { return pair.first; });
        }

        std::optional<ump::Endpoint> getEndpoint (const ump::EndpointId& id) const
        {
            if (const auto iter = infoForEndpoint.find (id); iter != infoForEndpoint.end())
            {
                if (iter->second.transport == TRANSPORT_BYTESTREAM)
                    return iter->second.getEquivalentBytestreamEndpoint();
            }

            if (const auto iter = connections.find (id); iter != connections.end())
            {
                if (auto connection = iter->second.lock())
                    return connection->getEndpoint();
            }

            return {};
        }

        std::optional<ump::StaticDeviceInfo> getStaticDeviceInfo (const ump::EndpointId& id) const
        {
            if (const auto iter = infoForEndpoint.find (id); iter != infoForEndpoint.end())
                return iter->second.getUmpStaticDeviceInfo();

            if (const auto iter = connections.find (id); iter != connections.end())
            {
                if (auto connection = iter->second.lock())
                    return connection->getStaticDeviceInfo();
            }

            return {};
        }

        std::shared_ptr<Connection> findOrMakeConnection (const ump::EndpointId& e)
        {
            const auto connection = connections.find (e);

            if (connection != connections.end())
                if (auto strong = connection->second.lock())
                    return strong;

            const auto info = infoForEndpoint.find (e);

            // We don't know anything about this endpoint, maybe it got disconnected.
            // In any case, we can't connect to it.
            if (info == infoForEndpoint.end())
                return {};

            const auto& staticDeviceInfo = info->second;

            switch (staticDeviceInfo.transport)
            {
                case TRANSPORT_BYTESTREAM:
                {
                    const auto result = BytestreamConnection::make (*deviceManager, staticDeviceInfo, e);
                    connections[e] = result;
                    return result;
                }

                case TRANSPORT_UMP:
                {
                    const auto result = UmpConnection::make (*deviceManager, staticDeviceInfo, e);
                    result->addEndpointListener (listener);
                    connections[e] = result;
                    return result;
                }
            }

            // Unknown transport!
            jassertfalse;
            return {};
        }

        static std::unique_ptr<Client> make (ump::EndpointsListener& l)
        {
            auto result = rawToUniquePtr (new Client { l });
            result->deviceManager = DeviceManager::getSingleton();

            if (result->deviceManager == nullptr)
                return {};

            result->deviceManager->addListener (*result);

            return result;
        }

        ~Client() override
        {
            if (deviceManager != nullptr)
                deviceManager->removeListener (*this);

            VirtualMidiPortManager::getSingleton().removeListener (*this);
        }

    private:
        explicit Client (ump::EndpointsListener& l)
            : listener (l)
        {
            // It's possible for the VirtualMidiPortManager to be created early on in the app's
            // lifecycle, even before the app's main window component peer is constructed. In the
            // case that the virtual port manager is constructed first, we still need to make a
            // record of these pre-existing ports in order to allow new connections to those ports.
            fetchVirtualConnections();
            VirtualMidiPortManager::getSingleton().addListener (*this);
        }

        void fetchVirtualConnections()
        {
            auto& manager = VirtualMidiPortManager::getSingleton();

            for (const auto& [port, transport] : { std::tuple (manager.getBytestreamConnection(), TRANSPORT_BYTESTREAM),
                                                   std::tuple (manager.getUmpConnection(), TRANSPORT_UMP) })
            {
                if (port != nullptr)
                    connections.insert_or_assign (port->getEndpointId(), port);
            }
        }

        void virtualConnectionAdded() override
        {
            fetchVirtualConnections();
            listener.virtualMidiServiceActiveChanged();
        }

        void virtualConnectionRemoved (std::shared_ptr<Connection> x) override
        {
            listener.virtualMidiServiceActiveChanged();
            const auto iter = connections.find (x->getEndpointId());

            if (iter != connections.end())
            {
                jassert (iter->second.lock() == x);
                connections.erase (iter);
            }
        }

        void deviceAdded (const StaticDeviceInfo& info) override
        {
            const auto endpointId = findUniqueId (info);
            endpointIdForDevice.insert_or_assign (info.id, endpointId);
            infoForEndpoint.insert_or_assign (endpointId, info);
            listener.endpointsChanged();
        }

        void deviceRemoved (int id) override
        {
            const auto iter = endpointIdForDevice.find (id);

            if (iter == endpointIdForDevice.end())
            {
                // That's weird, a device disconnected, but we had no record of it connecting in the first place!
                jassertfalse;
                return;
            }

            const auto connection = connections.find (iter->second);

            if (connection != connections.end())
            {
                if (const auto strong = connection->second.lock())
                    strong->disconnected();

                connections.erase (connection);
            }

            infoForEndpoint.erase (iter->second);
            endpointIdForDevice.erase (iter);
            listener.endpointsChanged();
        }

        ump::EndpointId findUniqueId (const StaticDeviceInfo& info) const
        {
            const auto base = std::invoke ([&]
            {
                if (info.serialNumber.isNotEmpty())
                    return info.serialNumber;

                if (info.name.isNotEmpty())
                    return info.name;

                // The device doesn't have any immediately-available useful info,
                // so we're giving up on creating a stable ID
                return String (info.id);
            });

            for (int suffix = 1;; ++suffix)
            {
                const auto potential = base + (suffix == 1 ? "" : " (" + String (suffix) + ')');
                const auto id = ump::EndpointId::makeSrcDst (potential, potential);

                if (infoForEndpoint.count (id) == 0)
                    return id;
            }
        }

        ump::EndpointsListener& listener;
        std::map<jint, ump::EndpointId> endpointIdForDevice;
        std::map<ump::EndpointId, StaticDeviceInfo> infoForEndpoint;
        std::map<ump::EndpointId, std::weak_ptr<Connection>> connections;
        std::shared_ptr<DeviceManager> deviceManager;
    };

    class InputImplNative : public ump::Input::Impl::Native,
                            private ump::Consumer
    {
    public:
        ~InputImplNative() override
        {
            connection->removeDisconnectionListener (disconnectionListener);
            connection->removeConsumer (*this);
        }

        ump::EndpointId getEndpointId() const override
        {
            return connection->getEndpointId();
        }

        ump::PacketProtocol getProtocol() const override
        {
            return converter.getProtocol();
        }

        static std::unique_ptr<InputImplNative> make (ump::DisconnectionListener& dl,
                                                      std::shared_ptr<Connection> d,
                                                      ump::PacketProtocol p,
                                                      ump::Consumer& c)
        {
            if (d == nullptr)
                return {};

            return rawToUniquePtr (new InputImplNative (dl, d, p, c));
        }

    private:
        InputImplNative (ump::DisconnectionListener& dl,
                         std::shared_ptr<Connection> d,
                         ump::PacketProtocol p,
                         ump::Consumer& c)
            : disconnectionListener (dl),
              consumer (c),
              converter (p),
              connection (std::move (d))
        {
            connection->addConsumer (*this);
            connection->addDisconnectionListener (disconnectionListener);
        }

        void consume (ump::Iterator b, ump::Iterator e, double time) override
        {
            converter.convert (b, e, [this, time] (auto view)
            {
                const ump::Iterator begin { view.data(), view.size() };
                consumer.consume (begin, std::next (begin), time);
            });
        }

        ump::DisconnectionListener& disconnectionListener;
        ump::Consumer& consumer;
        ump::GenericUMPConverter converter;
        std::shared_ptr<Connection> connection;
    };

    class OutputImplNative : public ump::Output::Impl::Native
    {
    public:
        ~OutputImplNative() override
        {
            connection->removeDisconnectionListener (disconnectionListener);
        }

        ump::EndpointId getEndpointId() const override
        {
            return connection->getEndpointId();
        }

        bool send (ump::Iterator b, ump::Iterator e) override
        {
            return connection->send (b, e);
        }

        static std::unique_ptr<OutputImplNative> make (ump::DisconnectionListener& dl,
                                                       std::shared_ptr<Connection> d)
        {
            if (d == nullptr)
                return {};

            return rawToUniquePtr (new OutputImplNative (dl, d));
        }

    private:
        OutputImplNative (ump::DisconnectionListener& dl, std::shared_ptr<Connection> d)
            : disconnectionListener (dl), connection (std::move (d))
        {
            connection->addDisconnectionListener (disconnectionListener);
        }

        ump::DisconnectionListener& disconnectionListener;
        std::shared_ptr<Connection> connection;
    };

    class SessionImplNative : public ump::Session::Impl::Native
    {
    public:
        String getName() const override
        {
            return name;
        }

        std::unique_ptr<ump::Input::Impl::Native> connectInput (ump::DisconnectionListener& d,
                                                                const ump::EndpointId& e,
                                                                ump::PacketProtocol p,
                                                                ump::Consumer& c) override
        {
            return InputImplNative::make (d, client->findOrMakeConnection (e), p, c);
        }

        std::unique_ptr<ump::Output::Impl::Native> connectOutput (ump::DisconnectionListener& d,
                                                                  const ump::EndpointId& e) override
        {
            return OutputImplNative::make (d, client->findOrMakeConnection (e));
        }

        std::unique_ptr<ump::VirtualEndpoint::Impl::Native> createNativeVirtualEndpoint (const String& endpointName,
                                                                                         const ump::DeviceInfo& info,
                                                                                         const String& productInstance,
                                                                                         ump::PacketProtocol protocol,
                                                                                         Span<const ump::Block> blocks,
                                                                                         ump::BlocksAreStatic blocksAreStatic) override
        {
            const auto impl = VirtualMidiPortManager::getSingleton().createNativeVirtualEndpoint (endpointName,
                                                                                                  info,
                                                                                                  productInstance,
                                                                                                  protocol,
                                                                                                  blocks,
                                                                                                  blocksAreStatic);

            if (impl == nullptr)
                return {};

            struct Result : public ump::VirtualEndpoint::Impl::Native
            {
                explicit Result (std::shared_ptr<VirtualEndpointImplNative> x) : impl (std::move (x)) {}

                ump::EndpointId getId() const override
                {
                    return impl->getId();
                }

                bool setBlock (uint8_t index, const juce::universal_midi_packets::Block& block) override
                {
                    return impl->setBlock (index, block);
                }

                bool setName (const juce::String& nameIn) override
                {
                    return impl->setName (nameIn);
                }

                std::shared_ptr<VirtualEndpointImplNative> impl;
            };

            return rawToUniquePtr (new Result { impl });
        }

        std::unique_ptr<ump::LegacyVirtualInput::Impl::Native> createLegacyVirtualInput (const String&) override
        {
            const auto impl = VirtualMidiPortManager::getSingleton().createLegacyVirtualInput();

            if (impl == nullptr)
                return {};

            struct Result : public ump::LegacyVirtualInput::Impl::Native
            {
                explicit Result (std::shared_ptr<LegacyVirtualPortImplNative> x) : impl (std::move (x)) {}

                ump::EndpointId getId() const override
                {
                    return impl->getId();
                }

                std::shared_ptr<LegacyVirtualPortImplNative> impl;
            };

            return rawToUniquePtr (new Result { impl });
        }

        std::unique_ptr<ump::LegacyVirtualOutput::Impl::Native> createLegacyVirtualOutput (const String&) override
        {
            const auto impl = VirtualMidiPortManager::getSingleton().createLegacyVirtualOutput();

            if (impl == nullptr)
                return {};

            struct Result : public ump::LegacyVirtualOutput::Impl::Native
            {
                explicit Result (std::shared_ptr<LegacyVirtualPortImplNative> x) : impl (std::move (x)) {}

                ump::EndpointId getId() const override
                {
                    return impl->getId();
                }

                std::shared_ptr<LegacyVirtualPortImplNative> impl;
            };

            return rawToUniquePtr (new Result { impl });
        }

        static std::unique_ptr<SessionImplNative> make (std::shared_ptr<Client> c, const String& n)
        {
            return rawToUniquePtr (new SessionImplNative { std::move (c), n });
        }

    private:
        explicit SessionImplNative (std::shared_ptr<Client> c, const String& n)
            : client (std::move (c)), name (n)
        {
        }

        std::shared_ptr<Client> client;
        String name;
    };

    class EndpointsImplNative : public ump::Endpoints::Impl::Native
    {
    public:
        ump::Backend getBackend() const override
        {
            return ump::Backend::android;
        }

        void getEndpoints (std::vector<ump::EndpointId>& buffer) const override
        {
            client->getEndpoints (buffer);
        }

        std::optional<ump::Endpoint> getEndpoint (const ump::EndpointId& x) const override
        {
            return client->getEndpoint (x);
        }

        std::optional<ump::StaticDeviceInfo> getStaticDeviceInfo (const ump::EndpointId& x) const override
        {
            return client->getStaticDeviceInfo (x);
        }

        std::unique_ptr<ump::Session::Impl::Native> makeSession (const String& name) override
        {
            return SessionImplNative::make (client, name);
        }

        bool isVirtualMidiBytestreamServiceActive() const override
        {
            return VirtualMidiPortManager::getSingleton().isBytestreamPortAvailable();
        }

        bool isVirtualMidiUmpServiceActive() const override
        {
            return VirtualMidiPortManager::getSingleton().isUmpPortAvailable();
        }

        void setVirtualMidiBytestreamServiceActive (bool x) override
        {
            VirtualMidiPortManager::setVirtualMidiBytestreamEnabled (x);
        }

        void setVirtualMidiUmpServiceActive (bool x) override
        {
            VirtualMidiPortManager::setVirtualMidiUmpEnabled (x);
        }

        static std::unique_ptr<EndpointsImplNative> make (ump::EndpointsListener& l)
        {
            if (auto c = Client::make (l))
                return rawToUniquePtr (new EndpointsImplNative { std::move (c) });

            return {};
        }

    private:
        explicit EndpointsImplNative (std::shared_ptr<Client> c)
            : client (std::move (c))
        {
        }

        std::shared_ptr<Client> client;
    };

    AndroidMidiHelpers() = delete;
};

auto ump::Endpoints::Impl::Native::make (EndpointsListener& l) -> std::unique_ptr<Native>
{
    return AndroidMidiHelpers::EndpointsImplNative::make (l);
}

} // namespace juce
